C#中async Task场景下关闭Form2触发TargetInvocationException的解决方法
问题修复方案
核心问题分析
- 静态CancellationTokenSource冲突:
cts被定义为static,多Form2实例会共享同一个取消源,导致取消逻辑混乱。 - FormClosed事件处理错误:关闭窗体时重新创建新的
CancellationTokenSource并调用Cancel,根本无法取消正在运行的DoSomeThing任务;反而再次调用DoSomeThing,完全偏离取消任务的目的。 - 任务访问已销毁控件:窗体关闭后
label1已被销毁,后台任务仍尝试访问其Text属性,这是抛出TargetInvocationException的直接原因。 - UI操作效率低下:循环中反复解析
label1.Text,既影响性能,也增加控件访问冲突概率。
修正后的代码
Form1代码(无需修改)
public Form1() { InitializeComponent(); } private void AddWindow_FormClosed(object sender, FormClosedEventArgs e) { this.Show(); } private void button1_Click(object sender, EventArgs e) { Form2 ukl = new Form2(); ukl.FormClosed += new FormClosedEventHandler(AddWindow_FormClosed); ukl.Show(); this.Hide(); }
Form2修正代码
private CancellationTokenSource _cts; private double _progress; // 用局部变量存储进度,避免反复访问UI控件 public Form2() { InitializeComponent(); _progress = 0; } private async void Form2_Load(object sender, EventArgs e) { _cts = new CancellationTokenSource(); try { await DoSomeThing(_cts.Token); } catch (OperationCanceledException) { // 任务被取消时的可选处理逻辑 } } async Task DoSomeThing(CancellationToken token) { while (_progress < 100 && !IsDisposed) // 检查窗体是否已销毁 { token.ThrowIfCancellationRequested(); // 主动抛出取消异常,终止任务 // 更新UI前先检查控件是否可用 if (!label1.IsDisposed) { label1.Text = _progress.ToString("F0"); } _progress += 1; await Task.Delay(100, token); // 传入token,让Delay也能响应取消 } } private void Form2_FormClosed(object sender, FormClosedEventArgs e) { // 取消并释放任务取消源 _cts?.Cancel(); _cts?.Dispose(); }
关键修改说明
- 移除static修饰符:将
cts改为实例私有字段_cts,每个Form2实例拥有独立的取消源,避免多实例冲突。 - 局部变量存储进度:用
_progress替代反复解析label1.Text,减少UI访问次数和字符串解析开销。 - 正确取消任务:FormClosed事件中直接调用已有
_cts的Cancel方法,同时释放资源,不再启动新任务。 - 检查控件状态:循环中添加
!IsDisposed和!label1.IsDisposed判断,避免访问已销毁的控件。 - Task.Delay传入token:让
Task.Delay响应取消请求,更快终止任务。 - 主动抛出取消异常:使用
token.ThrowIfCancellationRequested()替代简单return,确保任务正确终止并完成清理。
内容的提问来源于stack exchange,提问作者Dodge_this
相关产品推荐
相关产品推荐

