从 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
调用后修改的任何控件上的此标志(包括派生自 Control
的 Form
)。
但是,您需要了解任务如何处理异常:
如果你做一个 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 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);
我正在开发一个大型 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
调用后修改的任何控件上的此标志(包括派生自 Control
的 Form
)。
但是,您需要了解任务如何处理异常:
如果你做一个 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 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);