【问题标题】:control already disposed when returning from await从等待返回时已释放控件
【发布时间】:2020-03-22 08:12:44
【问题描述】:

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

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

我想确保当 UI 线程在已处理的表单中等待之后继续时我没有问题(如果用户在 Task 仍在运行时关闭了表单)。

我在谷歌上搜索了一下,找到了这个:

How to better handle disposed controls when using async/await

作者在此页面中写道,在上述情况下会引发异常(当 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";
    }

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

尽管 UI 线程尝试更改 Disposed 标签 (label1),但此代码运行时未引发任何异常,label1.IsDisposed 为“true”。

我是否遗漏了某些内容,或者自创建上述页面以来此功能是否发生了变化?

编辑:

根据要求,我运行的主要形式:

    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";
    }
}

我正在通过单击 button2 创建 Simple_Form。

我尝试通过单击 button1 或单击 Simple_Form 表单上的“X”按钮来处理它,这两种方式都可以正常工作,而不会引发任何异常。

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

【问题讨论】:

  • 你怎么称呼StartCheck?你能在你实际调用Dispose()表单的地方展示使用吗?
  • 没问题,我编辑了原帖。
  • 您应该收到关于不等待 newForm.startCheck(this); 的警告,startCheck 方法应该返回 Task
  • @Fabio 我刚刚用一个简单的(和同步的 POC)进行了验证。已处置的 Label 允许对其 Text 属性进行分配。
  • @Oguz Ozgul,所以在这种情况下更改标签文本不会抛出异常,在上述情况下,什么变化之王会抛出异常?我不知道是否必须检查 this.IsDisposed 在每次等待后确保在用户关闭表单或不需要时不会抛出异常(我不在乎代码是否会继续运行如果它最终没有任何问题结束,则在后台)。

标签: c# multithreading winforms dispose


【解决方案1】:

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

await Whatever();
if (IsDisposed)
    return;

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

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

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

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

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

但是,您需要了解 Tasks 是如何处理异常的:

如果你做一个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)

只有一个异步方法不应该返回 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);

【讨论】:

  • 哇,真是巧合。无论如何,我知道这种模式将解决这个问题,但我想弄清楚两件事:1.为什么在我创建的代码中没有抛出异常,哪些更改会抛出你在原始帖子中提到的异常。 2. 如果需要这种模式(如果我不在乎不必要的代码会运行,只要它在没有异常的情况下结束)。
  • @YakirShlezinger 阅读加粗的“然而”部分。您的代码应该是private async void button2_Click(object sender, EventArgs e) { await newForm.startCheck(this); } 这将允许您捕获异常。需要明确的是,如果您在 await Task.Delay 完成之前进行处置,则无论哪种方式都会发生异常,但它不会冒泡。请进行我建议的更改,然后重试,您应该会看到异常。
  • 我尝试进行你所说的更改(将 StartCheck 更改为 Task 并添加 await newForm.startCheck(this);),我仍然没有收到任何异常。
  • @YakirShlezinger 你是否在await 完成之前处理newForm?如果不是你很好。如果您认为有问题,请编辑问题,以便我们最终运行代码。
  • 是的,我是,label1.IsDisposed 在“label1.Text =...”之前为“True”。我按照建议编辑了原始问题。
猜你喜欢
  • 2019-11-22
  • 1970-01-01
  • 2018-11-08
  • 2015-01-25
  • 1970-01-01
  • 2010-11-27
  • 2019-06-28
  • 2019-03-25
  • 1970-01-01
相关资源
最近更新 更多