我正在处理一个大型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)
异步方法不应返回Task
或Task<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);