控件在从等待返回时已被处置



我正在处理一个大型WinForms项目,该项目控制同一UI线程上的多个窗体。

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

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

我在谷歌上搜索了一下,发现了这个:

如何在使用异步/等待时更好地处理已处理的控件

在本页中,作者写道,在上述情况下(当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() + "|startn";
label1.Text = "Thread:" + Thread.CurrentThread.ManagedThreadId.ToString() + "|start";
await Task.Delay(10000);
caller.richTextBox1.Text += "Thread:" + Thread.CurrentThread.ManagedThreadId.ToString() + "|stopn";
caller.richTextBox1.Text += "Thread:" + Thread.CurrentThread.ManagedThreadId.ToString() + "|" + label1.IsDisposed + "n";
label1.Text = "Thread:" + Thread.CurrentThread.ManagedThreadId.ToString() + "|stop";
}

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

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

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

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

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

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

await Whatever();
if (IsDisposed)
return;

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

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

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

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

具体而言,在同一方法(包括从Control派生的Form(中的await调用后修改的任何控件上检查此标志。

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

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

最新更新