在结束表格之前等待任务完成

Chr*_*ris 4 c# multithreading deadlock task thread-safety

如何使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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

Pet*_*iho 6

处理此问题的正确方法是取消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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

由表单B调用的Close()方法将立即返回,并且形成B的更改将使更新程序崩溃

由于您没有发布任何与"表单B"相关的代码,因此不清楚它为何或如何与当前Form1代码有关.很可能有一种很好的方法来修复这个"形式B",以便它更好地与Form1类和正在关闭的对象合作.但是,如果没有看到明确显示此交互的实际良好,最小,完整的代码示例,则无法建议这将如何工作.


坦率地说,阻止任何 UI事件处理程序是一个非常糟糕的主意.允许UI线程继续运行是非常重要的,否则就是邀请死锁.你当然在这里找到了一个死锁的例子.但是,即使你解决了这个特定的例子,你也可以避免所有其他的死锁事件.

阻止UI线程只是要求死锁,以及其他问题.

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


所有这一切,虽然我不能确定缺少一个好的代码示例,但我强烈怀疑你真的不应该首先拥有这个更新程序任务,而应该使用System.Windows.Forms.Timer该类.该类专门用于处理必须在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.
    ...
}
Run Code Online (Sandbox Code Playgroud)

由于System.Windows.Forms.TimerTick在UI线程上引发其事件,因此没有线程争用条件.如果您在FormClosing事件中停止计时器,那就是它.计时器停止了.当然,由于计时器的Tick事件是在UI线程上引发的,因此无需使用它Invoke()来执行代码.


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