Вот последовательность фрагментов кода, которые я недавно использовал, чтобы проиллюстрировать разницу и различные проблемы с помощью async.
Предположим, у вас есть обработчик событий в приложении на основе графического интерфейса, для которого требуется много времени, и поэтому вы хотите сделать его асинхронным. Вот синхронная логика вы начинаете с:
while (true) {
string result = LoadNextItem().Result;
if (result.Contains("target")) {
Counter.Value = result.Length;
break;
}
}
LoadNextItem возвращает задачу, которая в конечном итоге произвести некоторый результат, который вы хотели бы, чтобы проверить. Если текущим результатом является тот, который вы ищете, вы обновляете значение некоторого счетчика в пользовательском интерфейсе и возвращаетесь из метода. В противном случае вы продолжите обработку большего количества элементов из LoadNextItem.
Первая идея для асинхронной версии: просто используйте продолжения! И пока не будем игнорировать петлю. Я имею в виду, что может пойти не так?
return LoadNextItem().ContinueWith(t => {
string result = t.Result;
if (result.Contains("target")) {
Counter.Value = result.Length;
}
});
Отлично, теперь у нас есть метод, который не блокирует! Вместо этого он падает. Любые обновления элементов управления пользовательским интерфейсом должны происходить в потоке пользовательского интерфейса, поэтому вам нужно будет это учитывать. К счастью, есть возможность указать, как продолжения должны быть запланированы, и есть по умолчанию один раз для этого:
return LoadNextItem().ContinueWith(t => {
string result = t.Result;
if (result.Contains("target")) {
Counter.Value = result.Length;
}
},
TaskScheduler.FromCurrentSynchronizationContext());
Отлично, теперь у нас есть метод, который не врезаться! Вместо этого он терпит неудачу. Продолжения - это отдельные задачи сами по себе, причем их статус не привязан к статусу антецедентной задачи. Таким образом, даже если LoadNextItem будет работать с ошибками, вызывающий объект будет видеть только успешную задачу.Хорошо, тогда просто перейдите на исключение, если есть один:
return LoadNextItem().ContinueWith(t => {
if (t.Exception != null) {
throw t.Exception.InnerException;
}
string result = t.Result;
if (result.Contains("target")) {
Counter.Value = result.Length;
}
},
TaskScheduler.FromCurrentSynchronizationContext());
Отлично, теперь это действительно работает. Для одного элемента. Теперь, как насчет этого цикла. Оказывается, эквивалентное решением логики оригинальной синхронной версии будет выглядеть примерно так:
Task AsyncLoop() {
return AsyncLoopTask().ContinueWith(t =>
Counter.Value = t.Result,
TaskScheduler.FromCurrentSynchronizationContext());
}
Task<int> AsyncLoopTask() {
var tcs = new TaskCompletionSource<int>();
DoIteration(tcs);
return tcs.Task;
}
void DoIteration(TaskCompletionSource<int> tcs) {
LoadNextItem().ContinueWith(t => {
if (t.Exception != null) {
tcs.TrySetException(t.Exception.InnerException);
} else if (t.Result.Contains("target")) {
tcs.TrySetResult(t.Result.Length);
} else {
DoIteration(tcs);
}});
}
Или, вместо того, чтобы все вышеперечисленное, вы можете использовать асинхра сделать то же самое:
async Task AsyncLoop() {
while (true) {
string result = await LoadNextItem();
if (result.Contains("target")) {
Counter.Value = result.Length;
break;
}
}
}
Это намного приятнее сейчас, не так ли?
Если вы удалили вызов 'Wait' во втором примере *, то * два фрагмента были бы (в основном) эквивалентными. – Servy
Возможный дубликат [Is Async ожидает ключевое слово, эквивалентное продолжению с лямбдой?] (Http://stackoverflow.com/questions/8767218/is-async-await-keyword-equivalent-to-a-continuewith-lambda) –
FYI: Ваш метод getWebPage не может использоваться в обоих кодах. В первом коде он имеет тип «Задача», а во втором - тип «string». поэтому в основном ваш код не компилируется. - если быть точным. –