c# - 在关闭表单之前等待任务完成

标签 c# multithreading thread-safety task deadlock

如何让 FormClosing 事件处理程序(在 UI 线程上执行)等待一个任务完成,该任务确实调用了相同的表单?

    private void Form1_FormClosing(object sender, FormClosingEventArgs e)
    {
        cancelUpdater.Cancel(); // CancellationTokenSource
        if (!updater.IsCompleted)
        {
            this.Hide();
            updater.Wait(); // deadlock if updater task is inside invoke
        }
    }
    private void Form1_Shown(object sender, EventArgs e)
    {
        cancelUpdater = new CancellationTokenSource();
        updater = new Task(() => { Updater(cancelUpdater.Token); });
        updater.Start();
    }
    void Updater(CancellationToken cancellationToken)
    {
        while(!cancellationToken.IsCancellationRequested)
        {
            this.Invoke(new Action(() => {
                ...
            }));
            //Thread.Sleep(1000);
        }
    }

最佳答案

处理这个问题的正确方法是取消 Close事件,然后在任务完成时真正关闭表单。这可能看起来像这样:

private async void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
    cancelUpdater.Cancel(); // CancellationTokenSource
    if (!updater.IsCompleted)
    {
        this.Hide();
        e.Cancel = true;
        await updater;
        this.Close();
    }
}

现在,在您的评论中,您写道:

the Close() method called by a form B will return immediately and the changes that form B will make will crash the updater

由于您没有发布任何与“表单 B”相关的代码,因此不清楚它与当前 Form1 有任何关系的原因或方式。代码。很可能有一个很好的方法来修复这个“形式 B”,以便它与 Form1 更好地合作。类和正在关闭的对象。但是没有看到实际的 good, minimal, complete code example清楚地显示了这种相互作用,但无法说明它是如何工作的。


坦率地说,阻止任何 UI 事件处理程序并不是一个非常糟糕的主意。允许 UI 线程继续运行是非常重要的,否则就会引发死锁。您当然在这里找到了一个死锁示例。但是,即使您解决了这个特定示例,也远不能保证您可以避免所有其他死锁实例。

阻塞 UI 线程只会导致死锁等问题。

就是说,如果您不能用“表单 B”解决这个问题并且真的觉得您必须阻塞线程,您可以让跨线程调用使用 BeginInvoke()而不是 Invoke() (这使调用本身异步,因此您的“更新线程”将能够继续运行然后终止)。当然,如果您这样做,您将不得不更改代码以处理这样一个事实:当您调用的代码运行时,表单已经关闭。这可能是也可能不是一个简单的修复。


综上所述,虽然我不能确定缺少好的代码示例,但我强烈怀疑您真的不应该首先执行此更新程序任务,而应该使用 System.Windows.Forms.Timer类(class)。该类专门用于处理必须在 UI 线程中执行的周期性事件。

例如:

首先,拖放一个 Timer在设计器中将对象添加到您的表单上。默认情况下,名称将为 timer1 .然后设置 Interval您在任务中使用的 1000 毫秒延迟的属性。另外,更改您的 Updater()方法,因此它被声明为 timer1_Tick(object sender, EventArgs e)并将其用作计时器 Tick 的事件处理程序事件。

然后,更改您的代码,使其看起来像这样:

private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
    timer1.Stop();
}

private void Form1_Shown(object sender, EventArgs e)
{
    timer1.Start();
}

void timer1_Tick(object sender, EventArgs e)
{
    // All that will be left here is whatever you were executing in the
    // anonymous method you invoked. All that other stuff goes away.
    ...
}

System.Windows.Forms.Timer类(class)提高了它的Tick UI 线程上的事件,没有线程竞争条件。如果您在 FormClosing 中停止计时器事件,就是这样。计时器停止了。当然,由于计时器的 Tick事件在 UI 线程上引发,无需使用 Invoke()执行您的代码。


恕我直言,以上是根据问题中的信息可以提供的最佳答案。如果您觉得以上内容都没有用或不适用,请编辑您的问题以提供所有相关详细信息,包括一个好的代码示例。

关于c# - 在关闭表单之前等待任务完成,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29446542/

相关文章:

c# - 急切地处理 ManualResetEvent

Cocoa:以最轻量级的方式同步访问 double?

c# - EWS - 超时后续订?

c# - 没有时间部分的日期时间比较?

c# - 如果 DataContext 来自托管窗口,如何修改 UserControl 中绑定(bind)的 ObservableCollection?

Python:是在主线程中定义并从主线程或调用线程中的另一个运行中调用的函数

c++ - GDI+ 多线程绘图

c++ - 线程安全与迭代器有效性

java - 如何中断当前正在运行的线程?

c# - 有关 Javascript 到 C# 转换器的帮助