从 await 返回时控制已经被释放

control already disposed when returning from await

我正在开发一个大型 WinForms 项目,该项目在同一 UI 线程上控制多个表单。

其中一些表单能够从数据库中获取和分析一些数据,这是使用 await 完成的(为了在等待数据和分析数据时不冻结所有表单)。

我想确保当 UI 线程在等待处理后的表单中继续时我没有问题(如果用户关闭表单而任务仍在 运行宁).

我在 google 中进行了搜索,找到了这个:

在此页面中,作者写道,在上述情况下会抛出异常(当 UI 线程尝试访问已处置形式的标签时)。

我对这种情况进行了测试 运行,但没有抛出任何异常:

    public partial class Simple_Form : Form
{
    public Simple_Form()
    {
        InitializeComponent();

    }

    public async Task startCheck(Form1 caller)
    {
        caller.richTextBox1.Text += "Thread:" + Thread.CurrentThread.ManagedThreadId.ToString() + "|start\n";
        label1.Text = "Thread:" + Thread.CurrentThread.ManagedThreadId.ToString() + "|start";

        await Task.Delay(10000);
        caller.richTextBox1.Text += "Thread:" + Thread.CurrentThread.ManagedThreadId.ToString() + "|stop\n";
        caller.richTextBox1.Text += "Thread:" + Thread.CurrentThread.ManagedThreadId.ToString() + "|" + label1.IsDisposed + "\n";
        label1.Text = "Thread:" + Thread.CurrentThread.ManagedThreadId.ToString() + "|stop";
    }

我试图 运行 StartCheck 并在 UI 线程处于等待状态时关闭 Simple_Form 表单。

尽管 UI 线程尝试更改 Disposed 标签 (label1),此代码 运行ning 没有抛出任何异常,label1.IsDisposed 是 "true".

我是不是遗漏了什么,或者自从上面的页面创建以来,这个功能是否发生了变化?

编辑:

按要求,主窗体i 运行:

    public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }
    Simple_Form newForm;
    private async void button2_Click(object sender, EventArgs e)
    {
        newForm = new Simple_Form();
        newForm.Show();

        await newForm.startCheck(this);

        return;

    }
    private void button1_Click(object sender, EventArgs e)
    {
        newForm.Dispose();
    }

    private void button3_Click(object sender, EventArgs e)
    {
        richTextBox1.Text += "Thread:" + Thread.CurrentThread.ManagedThreadId.ToString() + "|Still alive.\n";
    }
}

我正在通过单击按钮 2 创建 Simple_Form。

我尝试通过单击按钮 1 或仅单击 Simple_Form 表单上的 "X" 按钮来处理它,这两种方法都没有抛出任何异常。

编辑 2:按照建议更改了代码,原来的问题仍然存在。

有趣,这是我的相关问题。无论如何,解决方案很简单。使用此模式:

await Whatever();
if (IsDisposed)
    return;

为什么这是必要的?那么 await 调用捕获当前的 SynchronizationContext 然后回发给它。

这意味着您回到了原来的话题。在这种情况下,GUI 线程。

虽然这是异步发生的,但 GUI 对象可能会因各种原因(最常见的是用户关闭的表单)而被处理掉。请记住,await 不是 阻塞调用。

因此,您每次在 GUI 线程上 await 时都应该使用 IsDisposed 检查来保护自己。

具体来说,检查在同一方法中 await 调用后修改的任何控件上的此标志(包括派生自 ControlForm)。

但是,您需要了解任务如何处理异常:

如果你做一个 await 你可以 try ... catch 绕过它。如果您 使用await 异常就不会冒泡。这是一个简单的例子。

Task.Run(() => { ... });

这将 不会 引发异常,您可以捕获 除非 正在等待它。如果您不使用 await,您可以使用 Task.Exception 检查异常,如下所示:

var task = Task.Run(() => { ... });
//...SNIP...
if (task.Exception != null)
    //Do something

您的代码的其他问题:

public async void StartCheck(Form1 caller)

应该是

public async Task StartCheck(Form1 caller)

只有 一次异步方法不应该return TaskTask<T> 是如果你不允许使用那个签名(如按钮点击处理程序)。

最后,使用 Task.Delay 而不是 Thread.Sleep。变化

await Task.Run(() =>
{
    Thread.Sleep(10000);
});

await Task.Delay(10000);

编辑

试试这个:

public async Task startCheck(Form1 caller)
{
    await Task.Delay(10000);
    this.Show();
}

在您关闭 newForm 之后但在 await 完成之前。将抛出异常。

这也会导致您预期的行为:

newForm.Dispose(true);