在C#中,Task是异步编程的核心类型,它代表一个异步操作。它是对线程的更高级抽象,大大简化了异步和并行编程。
Task 状态:
- Created: 任务已通过构造函数创建,但尚未被安排(例如,通过Start()或TaskFactory.StartNew()方法安排执行)。
- WaitingForActivation: 任务正在等待被安排到任务调度程序。这种状态通常用于由异步操作(如async/await)创建的任务,或者通过TaskCompletionSource创建的任务。
- WaitingToRun: 任务已被安排执行,但尚未开始执行。它可能在等待线程可用。
- Running: 正在运行,但尚未完成。
- WaitingForChildrenToComplete:任务已经完成其本身的执行,并正在等待附加的子任务完成。只有当任务创建了子任务,并且父任务配置为等待子任务完成时,才会进入此状态。
- RanToCompletion: 成功完成,没有抛出未处理的异常,也没有被取消。
- Faulted: 因未处理的异常而完成。
- Canceled: 任务已通过取消操作完成。这通常是由于CancellationToken被触发,任务通过抛出OperationCanceledException来响应取消请求。
创建和启动 Task
1.使用 Task.Run() (最常用)
用于将工作项排队到线程池,并返回一个表示该工作的 Task 或 Task。
// 用于无返回值的方法
Task task = Task.Run(() =>
{
Console.WriteLine("在线程池线程上执行的任务");
Thread.Sleep(1000);
});
task.Wait(); // 阻塞等待任务完成
// 用于有返回值的方法
Task<int> taskWithResult = Task.Run(() =>
{
Console.WriteLine("计算中...");
return 42;
});
2.使用 Task.Factory.StartNew()
比 Task.Run 提供更多选项(如任务创建选项、自定义调度器),但在大多数简单场景下,Task.Run 是首选。
ask task = Task.Factory.StartNew(() =>
{
// 工作代码
}, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
3.使用 async/await 关键字 (现代方式)
async 方法会自动将代码包装成状态机,并返回一个 Task 或 Task。
public async Task<string> DownloadStringAsync(string url)
{
using (var httpClient = new HttpClient())
{
// await 会挂起当前方法,将控制权交回调用者,但不会阻塞线程。
string result = await httpClient.GetStringAsync(url);
// 当后台操作完成後,方法会在此处恢复执行。
return result;
}
}
// 调用
var downloadTask = DownloadStringAsync("https://example.com");
// ... 这里可以做其他事情 ...
string content = await downloadTask; // 等待任务完成并获取结果
4.使用 Task.FromResult (用于同步返回结果)
当你有一个结果需要包装成 Task 以满足接口要求时非常有用,常用于模拟异步操作或缓存的值。
public Task<int> GetCachedNumberAsync()
{
int cachedValue = 42; // 从缓存中同步获取
return Task.FromResult(cachedValue); // 无需启动新线程
}
5.使用 Task.Delay (用于等待一段时间)
创建一个在指定时间后完成的任务,常用于异步等待。
ublic async Task ProcessAsync()
{
await Task.Delay(2000); // 异步等待2秒,不阻塞线程
Console.WriteLine("2秒后执行");
}
等待和获取结果
1.await 运算符 (推荐)
- 非阻塞地等待任务完成。它会将方法后续代码包装为任务的“延续”,并在任务完成后执行。
- 只能在 async 方法中使用。
sync Task MyMethod()
{
await SomeAsyncOperation();
Console.WriteLine("SomeAsyncOperation 完成后才执行");
}
2.Task.Result 和 Task.Wait() (不推荐,容易死锁)
- 这两个属性/方法会阻塞当前线程,直到任务完成。
- 在 UI 线程或 ASP.NET 的请求上下文中调用时,极易引起死锁。
- 黄金法则: 在异步代码中,尽量使用 await,避免使用 .Result 或 .Wait()。
/ 危险代码(在UI线程中调用会导致死锁)
var result = GetAsyncResult().Result;
GetAsyncResult().Wait();
3.Task.WaitAll() 和 Task.WaitAny() (阻塞式等待多个任务)
阻塞当前线程,等待提供的所有任务或任一任务完成。
ask task1 = Task.Delay(1000);
Task task2 = Task.Delay(2000);
Task.WaitAll(task1, task2); // 阻塞,直到两个任务都完成
Console.WriteLine("Both done!");
int firstFinishedIndex = Task.WaitAny(task1, task2); // 阻塞,直到任一任务完成
Console.WriteLine($"Task {firstFinishedIndex} finished first.");
4.Task.WhenAll() 和 Task.WhenAny() (非阻塞式等待多个任务)
返回一个新的 Task,该任务会在所有提供任务或任一任务完成时完成。通常与 await 一起使用。
async Task ProcessMultipleTasks()
{
Task<int> task1 = FetchData1Async();
Task<string> task2 = FetchData2Async();
// 非阻塞地等待所有任务完成
var results = await Task.WhenAll(task1, task2);
int data1 = results[0];
string data2 = results[1];
// 或者,非阻塞地等待任一任务完成
Task firstFinishedTask = await Task.WhenAny(task1, task2);
if (firstFinishedTask == task1)
{
Console.WriteLine("FetchData1Async 先完成");
}
}
异常处理
Task 中的异常会被捕获并包装在 AggregateException 中。使用 await 时,它会自动解包 AggregateException,抛出最初的异常,使处理更自然。
// 1. 使用 try-catch 与 await (推荐)
try
{
await SomeOperationThatMightFailAsync();
}
catch (InvalidOperationException ex)
{
// 直接捕获具体的异常
Console.WriteLine(ex.Message);
}
// 2. 处理未使用 await 的任务的异常
Task faultyTask = Task.Run(() => throw new Exception("Oops!"));
try
{
faultyTask.Wait(); // 或者访问 .Result
}
catch (AggregateException ae)
{
// 必须处理 AggregateException
foreach (var e in ae.InnerExceptions)
{
Console.WriteLine(e.Message);
}
}
// 3. 检查任务的 Exception 属性
if (faultyTask.IsFaulted)
{
foreach (var e in faultyTask.Exception.InnerExceptions)
{
Console.WriteLine(e.Message);
}
}
取消操作
使用 CancellationTokenSource 和 CancellationToken 来协作式地取消任务。
public async Task DoLongRunningWorkAsync(CancellationToken cancellationToken = default)
{
for (int i = 0; i < 100; i++)
{
// 检查是否请求了取消
cancellationToken.ThrowIfCancellationRequested();
// 或者轮询检查
// if (cancellationToken.IsCancellationRequested)
// {
// // 进行清理工作,然后...
// throw new OperationCanceledException(cancellationToken);
// }
await Task.Delay(1000, cancellationToken); // Delay 也支持取消
Console.WriteLine($"Working... {i}");
}
}
// 调用方
var cts = new CancellationTokenSource();
try
{
// 在5秒后自动取消
cts.CancelAfter(5000);
await DoLongRunningWorkAsync(cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("任务被取消了。");
}
finally
{
cts.Dispose();
}
任务延续
ContinueWith 创建一个会在指定任务完成后立即执行的延续任务,无论前驱任务是成功完成、失败还是被取消。
Task initialTask = Task.Run(() => Console.WriteLine("初始任务"));
// 当前一个任务完成后执行延续任务
Task continuationTask = initialTask.ContinueWith(previousTask =>
{
Console.WriteLine("前一个任务已完成");
});
// 带条件的延续
initialTask.ContinueWith(t =>
{
Console.WriteLine("只在任务失败时执行");
}, TaskContinuationOptions.OnlyOnFaulted);
initialTask.ContinueWith(t =>
{
Console.WriteLine("只在任务成功时执行");
}, TaskContinuationOptions.OnlyOnRanToCompletion);
initialTask.ContinueWith(t =>
{
Console.WriteLine("只在任务被取消时执行");
}, TaskContinuationOptions.OnlyOnCanceled);