Ответ
ContinueWith — это метод класса Task, который позволяет создать продолжение (continuation): задачу, которая автоматически выполняется после завершения исходной задачи (предшественника), независимо от ее статуса (успех, ошибка, отмена).
Базовый синтаксис:
Task firstTask = Task.Run(() => DoWork());
Task continuationTask = firstTask.ContinueWith(previousTask =>
{
// Этот код выполнится после завершения firstTask
Console.WriteLine($"Первая задача завершена со статусом: {previousTask.Status}");
});
Зачем это нужно?
Для организации цепочек асинхронных операций, где каждая следующая зависит от результата или факта завершения предыдущей, без блокировки потока ожиданием (Wait или Result).
Ключевые параметры TaskContinuationOptions:
С их помощью можно точно контролировать, когда и при каких условиях должно запуститься продолжение.
Task.Run(() => ParseData())
.ContinueWith(prevTask =>
{
// Выполнится ТОЛЬКО если первая задача завершилась успешно (RanToCompletion)
ProcessResult(prevTask.Result);
}, TaskContinuationOptions.OnlyOnRanToCompletion)
.ContinueWith(prevTask =>
{
// Выполнится ТОЛЬКО если первая задача была отменена (Canceled)
LogCancellation();
}, TaskContinuationOptions.OnlyOnCanceled)
.ContinueWith(prevTask =>
{
// Выполнится ТОЛЬКО если в первой задаче было исключение (Faulted)
// prevTask.Exception содержит AggregateException
LogError(prevTask.Exception?.InnerException);
}, TaskContinuationOptions.OnlyOnFaulted);
Важные особенности и современная альтернатива:
- Обработка исключений: Исключение из предшественника не приведет к краху продолжения. Оно "упаковывается" в свойство
Exceptionзадачи-продолжения (типAggregateException). - Планировщик задач: Можно указать
TaskScheduler(например,TaskScheduler.FromCurrentSynchronizationContext()для возврата в UI-поток). -
Современная альтернатива —
async/await: В большинстве случаев код сContinueWithменее читаем. Использованиеasync/awaitпредпочтительнее.Старый стиль с
ContinueWith:Task.Factory.StartNew(() => "http://example.com/data.json") .ContinueWith(prev => DownloadStringAsync(prev.Result)) .Unwrap() // Распаковываем вложенную задачу .ContinueWith(prev => JsonConvert.DeserializeObject<Data>(prev.Result));Современный стиль с
async/await:public async Task<Data> GetDataAsync() { string url = await Task.Run(() => "http://example.com/data.json"); string json = await DownloadStringAsync(url); return JsonConvert.DeserializeObject<Data>(json); }
Когда все еще полезен ContinueWith?
- При работе с динамически создаваемыми цепочками задач.
- Для низкоуровневой настройки поведения продолжения (например,
ExecuteSynchronouslyдля выполнения в том же потоке). - В библиотечном коде, где нельзя использовать
async/await.