Я бы не стал использовать отдельное продолжение ContinueWith
для успешных и ошибочных сценариев. Я бы предпочел обрабатывать оба случая в одном месте, используя try/catch
:
task.ContinueWith(t =>
{
try
{
// this would re-throw an exception from task, if any
var result = t.Result;
// process result
lbUsers.DataSource = JsonConvert.DeserializeObject<List<string>>(result);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
lbUsers.Clear();
lbUsers.Items.Add("Error loading users!");
}
},
CancellationToken.None,
TaskContinuationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext()
);
Если t
является неуниверсальным Task
(а не Task<TResult>
), вы можете сделать t.GetAwaiter().GetResult()
, чтобы повторно создать исходное исключение внутри лямбды ContinueWith
; t.Wait()
тоже подойдет. Будьте готовы обработать AggregatedException
, вы можете получить внутреннее исключение примерно так:
catch (Exception ex)
{
while (ex is AggregatedException && ex.InnerException != null)
ex = ex.InnerException;
MessageBox.Show(ex.Message);
}
Если вы имеете дело с серией ContinueWith
, обычно вам не нужно обрабатывать исключения внутри каждого ContinueWith
. Сделайте это один раз для самой внешней результирующей задачи, например:
void GetThreePagesV1()
{
var httpClient = new HttpClient();
var finalTask = httpClient.GetStringAsync("http://example.com")
.ContinueWith((task1) =>
{
var page1 = task1.Result;
return httpClient.GetStringAsync("http://example.net")
.ContinueWith((task2) =>
{
var page2 = task2.Result;
return httpClient.GetStringAsync("http://example.org")
.ContinueWith((task3) =>
{
var page3 = task3.Result;
return page1 + page2 + page3;
}, TaskContinuationOptions.ExecuteSynchronously);
}, TaskContinuationOptions.ExecuteSynchronously).Unwrap();
}, TaskContinuationOptions.ExecuteSynchronously).Unwrap()
.ContinueWith((resultTask) =>
{
httpClient.Dispose();
string result = resultTask.Result;
try
{
MessageBox.Show(result);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
},
CancellationToken.None,
TaskContinuationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());
}
Любые исключения, созданные внутри внутренних задач, будут распространяться на самую внешнюю ContinueWith
лямбду, когда вы получаете доступ к результатам внутренних задач (taskN.Result
).
Этот код функционален, но он уродлив и нечитаем. Разработчики JavaScript называют это Пирамида обратного вызова судьбы. У них есть Promises, чтобы справиться с этим. У разработчиков C# есть async/await
, который вы, к сожалению, не можете использовать из-за ограничения VS2010.
ИМО, ближе всего к обещаниям JavaScript в TPL находится Стивен. Выкройка Then
Туба. И ближе всего к async/await
в C# 4.0 его шаблон Iterate
из того же сообщения в блоге, в котором используется функция C# yield
.
Используя шаблон Iterate
, приведенный выше код можно было бы переписать в более читабельном виде. Обратите внимание, что внутри GetThreePagesHelper
вы можете использовать все знакомые операторы синхронного кода, такие как using
, for
, while
, try/catch
и т. д. Однако важно понимать поток асинхронного кода этого шаблона:
void GetThreePagesV2()
{
Iterate(GetThreePagesHelper()).ContinueWith((iteratorTask) =>
{
try
{
var lastTask = (Task<string>)iteratorTask.Result;
var result = lastTask.Result;
MessageBox.Show(result);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
throw;
}
},
CancellationToken.None,
TaskContinuationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());
}
IEnumerable<Task> GetThreePagesHelper()
{
// now you can use "foreach", "using" etc
using (var httpClient = new HttpClient())
{
var task1 = httpClient.GetStringAsync("http://example.com");
yield return task1;
var page1 = task1.Result;
var task2 = httpClient.GetStringAsync("http://example.net");
yield return task2;
var page2 = task2.Result;
var task3 = httpClient.GetStringAsync("http://example.org");
yield return task3;
var page3 = task3.Result;
yield return Task.Delay(1000);
var resultTcs = new TaskCompletionSource<string>();
resultTcs.SetResult(page1 + page1 + page3);
yield return resultTcs.Task;
}
}
/// <summary>
/// A slightly modified version of Iterate from
/// http://blogs.msdn.com/b/pfxteam/archive/2010/11/21/10094564.aspx
/// </summary>
public static Task<Task> Iterate(IEnumerable<Task> asyncIterator)
{
if (asyncIterator == null)
throw new ArgumentNullException("asyncIterator");
var enumerator = asyncIterator.GetEnumerator();
if (enumerator == null)
throw new InvalidOperationException("asyncIterator.GetEnumerator");
var tcs = new TaskCompletionSource<Task>();
Action<Task> nextStep = null;
nextStep = (previousTask) =>
{
if (previousTask != null && previousTask.Exception != null)
tcs.SetException(previousTask.Exception);
if (enumerator.MoveNext())
{
enumerator.Current.ContinueWith(nextStep,
TaskContinuationOptions.ExecuteSynchronously);
}
else
{
tcs.SetResult(previousTask);
}
};
nextStep(null);
return tcs.Task;
}
person
noseratio
schedule
10.03.2014