ContinueWith
是 TPL (Task Parallel Library) 中用于任务延续的重要方法,它允许在一个任务完成后自动执行后续操作。下面我将全面解析其用法和最佳实践。
一、基本语法
public Task ContinueWith(
Action<Task> continuationAction,
TaskScheduler scheduler = null,
TaskContinuationOptions continuationOptions = TaskContinuationOptions.None,
CancellationToken cancellationToken = default
)
二、核心功能
-
任务链式执行:在前置任务完成后自动触发后续操作
-
多任务协调:处理多个任务的执行顺序和依赖关系
-
异常传播:自动处理前置任务的异常状态
三、常用重载方法
1. 基本延续
task.ContinueWith(previousTask => { // 前置任务完成后执行的代码 });
2. 指定任务调度器
task.ContinueWith(previousTask => { // UI线程更新 }, TaskScheduler.FromCurrentSynchronizationContext());
3. 带返回值的延续
Task<string> continuation = task.ContinueWith(previousTask => { return "处理结果: " + previousTask.Result; });
4. 带条件选项的延续
task.ContinueWith(previousTask => { // 只在任务成功完成时执行 }, TaskContinuationOptions.OnlyOnRanToCompletion);
四、关键参数详解
1. TaskContinuationOptions 常用选项
选项 | 描述 |
---|---|
None | 默认选项,无论前置任务状态如何都会执行 |
OnlyOnRanToCompletion | 仅在前置任务成功完成时执行 |
OnlyOnFaulted | 仅在前置任务出错时执行 |
OnlyOnCanceled | 仅在前置任务被取消时执行 |
NotOnRanToCompletion | 前置任务未成功完成时执行 |
ExecuteSynchronously | 尝试同步执行延续任务 |
2. TaskScheduler 选择
-
TaskScheduler.Default
:线程池调度器 -
TaskScheduler.FromCurrentSynchronizationContext()
:UI线程调度器 -
TaskScheduler.Current
:当前任务的调度器
五、典型使用场景
1. UI线程更新
Task.Run(() => { // 后台工作 }).ContinueWith(t => { textBox1.Text = t.Result; // 错误!跨线程访问 }, TaskScheduler.FromCurrentSynchronizationContext()); // 正确方式
2. 异常处理
Task.Run(() => { throw new Exception("测试异常"); }).ContinueWith(t => { if (t.IsFaulted) { Console.WriteLine($"异常: {t.Exception.InnerException.Message}"); } });
3. 多任务串联
Task.Run(() => 10) .ContinueWith(t => t.Result * 2) .ContinueWith(t => t.Result + 5) .ContinueWith(t => Console.WriteLine($"最终结果: {t.Result}"));
六、与async/await对比
特性 | ContinueWith | async/await |
---|---|---|
代码风格 | 回调式 | 线性同步式 |
异常处理 | 需检查IsFaulted | 直接try-catch |
上下文恢复 | 需手动指定 | 自动恢复 |
可读性 | 较低 | 较高 |
灵活性 | 更高 | 相对固定 |
七、最佳实践
-
始终处理异常:
task.ContinueWith(t => { if (t.IsFaulted) { /* 处理异常 */ } }, TaskContinuationOptions.OnlyOnFaulted);
-
避免嵌套过深:
// 不推荐 task.ContinueWith(t1 => { t1.ContinueWith(t2 => { t2.ContinueWith(t3 => { /* ... */ }); }); }); // 推荐 task.ContinueWith(t1 => { /* ... */ }) .ContinueWith(t2 => { /* ... */ }) .ContinueWith(t3 => { /* ... */ });
-
资源清理:
var cts = new CancellationTokenSource(); Task.Run(() => { /* ... */ }, cts.Token) .ContinueWith(t => { cts.Dispose(); // 及时释放资源 });
-
性能优化:
// 对于简单延续,考虑同步执行 task.ContinueWith(t => Console.WriteLine("完成"), TaskContinuationOptions.ExecuteSynchronously);
八、常见问题解决方案
-
跨线程访问UI控件:
Task.Run(() => 42).ContinueWith(t => { label1.Invoke((Action)(() => label1.Text = t.Result.ToString())); });
-
取消延续任务:
var cts = new CancellationTokenSource(); Task.Run(() => { /* ... */ }) .ContinueWith(_ => { /* ... */ }, cts.Token); // 取消延续 cts.Cancel();
-
处理AggregateException:
task.ContinueWith(t => { if (t.Exception != null) { foreach (var ex in t.Exception.InnerExceptions) { Console.WriteLine(ex.Message); } } });
ContinueWith
提供了强大的任务延续能力,但在现代C#开发中,大多数场景下推荐优先使用 async/await
,它提供了更简洁直观的代码结构。保留 ContinueWith
用于需要更精细控制任务调度的复杂场景。