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

Waiting for task to finish before closing form

如何让 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

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


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

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

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


综上所述,虽然我不能确定缺少好的代码示例,但我强烈怀疑您真的不应该首先执行此更新程序任务,而应该使用 System.Windows.Forms.Timer class . 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 在 UI 线程上引发其 Tick 事件,因此不存在线程竞争条件。如果您在 FormClosing 事件中停止计时器,就是这样。计时器停止了。当然,由于计时器的 Tick 事件是在 UI 线程上引发的,因此无需使用 Invoke() 来执行您的代码。


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