异步进度窗体异常未被指定try/catch捕获的原因及解决方法
问题原因分析
你的异常没被外层try/catch捕获,核心原因有两个:
async void方法的异常处理特性
你的PerformTasks是async void方法,这种方法的异常不会像async Task那样被封装在返回的Task中,而是会直接抛到当前同步上下文的线程(也就是UI主线程)上,这就触发了你注册的Application.ThreadException事件,而外层的try/catch完全捕捉不到这个异常——因为async void方法的调用者无法追踪它的异常。ShowDialog与异步逻辑的脱节
你用ShowDialog()阻塞主线程等待窗体关闭,但PerformTasks是异步执行的,外层的try/catch只包裹了ShowDialog()的执行过程,根本覆盖不到异步任务里抛出的异常。
解决方案:改用
async Task+暴露任务引用 要让异常能被外层try/catch捕获,我们需要调整异步方法的返回类型,并让调用者能追踪到异步任务的状态。
第一步:修改ProgressForm的核心代码
把async void改成async Task,同时在窗体里暴露一个CompletionTask属性,用来保存异步任务的引用:
public ProgressForm(IEnumerable<KeyValuePair<string, Action>> tasks) { InitializeComponent(); this.tasks = tasks; } // 暴露给外部的任务引用 public Task CompletionTask { get; private set; } protected override void OnShown(EventArgs e) { base.OnShown(e); // 把异步任务赋值给属性,让外部可以追踪 CompletionTask = PerformTasks(tasks); } // 把async void改成async Task private async Task PerformTasks(IEnumerable<KeyValuePair<string, Action>> tasks) { tokenSource = new CancellationTokenSource(); var token = tokenSource.Token; try { await Task.Run(() => { foreach (var task in tasks) { token.ThrowIfCancellationRequested(); task.Value(); } }, token); isTaskCompleted = true; } catch (OperationCanceledException oex) when (oex.CancellationToken == token) { // 取消操作的异常可以在这里处理,或者直接忽略 } // 不要吞掉其他异常,让它传递到外层 finally { tokenSource.Dispose(); DialogResult = isTaskCompleted ? DialogResult.OK : DialogResult.Cancel; Close(); } }
第二步:修改调用代码,追踪异步任务
在调用窗体时,先ShowDialog(),然后await窗体的CompletionTask,这样外层的try/catch就能捕获任务中抛出的异常了:
try { using (var progress = new ProgressForm(new List<KeyValuePair<string, Action>>() { new KeyValuePair<string, Action>("任务1", () => SomeLongRunningTask()) })) { progress.ShowDialog(); // 等待异步任务完成,捕获其中的异常 await progress.CompletionTask; }; } catch (MyException ex) { // 现在这里可以正常捕获到异常了 // Do something }
补充说明
- 为什么这样能行?
async Task方法的异常会被封装在返回的Task对象中,当你await这个Task时,异常会被重新抛出,正好被外层的try/catch捕获。 - 如果你的调用代码不在异步方法中(比如在同步的UI事件处理程序里),可以用
progress.CompletionTask.Wait(),但要注意避免死锁——不过因为PerformTasks里用了Task.Run把任务放到后台线程,这里用Wait()一般不会有问题,更推荐的是把调用代码改成异步的(比如用async void的事件处理程序)。
内容的提问来源于stack exchange,提问作者Ivan-Mark Debono
相关产品推荐
相关产品推荐

