2014-11-20 2 views
11

Я обновляю свой набор навыков параллелизма. Моя проблема кажется довольно распространенной: читайте из нескольких Uris, разбирайте и работайте с результатом и т. Д. У меня есть Concurrency in C# Cookbook. Есть несколько примеров использования GetStringAsync, такие какКак выполнять несколько задач, обрабатывать исключения и возвращать результаты

static async Task<string> DownloadAllAsync(IEnumerable<string> urls) 
{ 
    var httpClient = new HttpClient(); 

    var downloads = urls.Select(url => httpClient.GetStringAsync(url)); 

    Task<string>[] downloadTasks = downloads.ToArray(); 

    string[] htmlPages = await Task.WhenAll(downloadTasks); 

    return string.Concat(htmlPages); 
} 

Что мне нужно, это асинхронный шаблон для запуска несколько асинхронных задач, захватив полный или частичный успех.

  1. Url 1 преуспевает
  2. Url 2 успешно
  3. Url 3 выходит из строя (тайм-аут, плохой формат Ури, 401 и т.д.)
  4. Url 4 успешно
  5. ... 20 больше с переменным успехом

Ожидание в задаче DownloadAllAsync будет генерировать одно обобщенное исключение, если какой-либо сбой, сбросив накопленные результаты. Из моих ограниченных исследований, когда WhenAll или WaitAll ведут себя одинаково. Я хочу поймать исключения, зарегистрировать неудачи, но продолжать с оставшимися задачами, даже если они все терпят неудачу. Я мог бы обрабатывать их один за другим, но разве это не победит цель позволить TPL управлять всем процессом? Есть ли ссылка на шаблон, который бы выполнил это в чистом виде TPL? Возможно, я использую неправильный инструмент?

+0

Как вы относитесь к исключениям? –

ответ

14

Я хочу поймать исключения, зарегистрировать ошибки, но продолжаю выполнять оставшиеся задачи, даже если все они не работают.

В этом случае самым чистым решением является изменение того, что делает ваш код для каждого элемента. I.e., этот действующий код:

var downloads = urls.Select(url => httpClient.GetStringAsync(url)); 

говорит: «для каждого URL-адреса загрузите строку». То, что вы хотите сказать, «для каждого URL-адреса, загрузить строку, а затем зарегистрировать и проигнорировать любые ошибки»:

static async Task<string> DownloadAllAsync(IEnumerable<string> urls) 
{ 
    var httpClient = new HttpClient(); 
    var downloads = urls.Select(url => TryDownloadAsync(httpClient, url)); 
    Task<string>[] downloadTasks = downloads.ToArray(); 
    string[] htmlPages = await Task.WhenAll(downloadTasks); 
    return string.Concat(htmlPages); 
} 

static async Task<string> TryDownloadAsync(HttpClient client, string url) 
{ 
    try 
    { 
    return await client.GetStringAsync(url); 
    } 
    catch (Exception ex) 
    { 
    Log(ex); 
    return string.Empty; // or whatever you prefer 
    } 
} 
+0

Прохладный степен, Гораздо лучший ответ, чем мой (+1) :) –

+0

Стивен, большое спасибо. Есть ли какая-то особая причина, по которой вы используете статические методы в своих примерах здесь и в книге? Просто более кратким и легче работать в примерном решении? Или еще одна причина? –

+0

Да. Об этом тоже спросили технические рецензенты. :) Я сохранил методы 'static', потому что они тонко применяют понятие о том, что методы async лучше работают без состояния. –

3

Вы можете приложить продолжение для всех своих задач и дождаться их, а не ждать непосредственно задач.

static async Task<string> DownloadAllAsync(IEnumerable<string> urls) 
{ 
    var httpClient = new HttpClient(); 

    IEnumerable<Task<Task<string>>> downloads = urls.Select(url => httpClient.GetStringAsync(url).ContinueWith(p=> p, TaskContinuationOptions.ExecuteSynchronously)); 

    Task<Task<string>>[] downloadTasks = downloads.ToArray(); 

    Task<string>[] compleTasks = await Task.WhenAll(downloadTasks); 

    foreach (var task in compleTasks) 
    { 
     if (task.IsFaulted)//Or task.IsCanceled 
     { 
      //Handle it 
     } 
    } 

    var htmlPages = compleTasks.Where(x => x.Status == TaskStatus.RanToCompletion) 
     .Select(x => x.Result); 

    return string.Concat(htmlPages); 
} 

Это не остановится, как только одна задача завершится неудачей, скорее она будет ждать выполнения всех задач. Затем обрабатывайте успех и отказ отдельно.

+1

вы можете сделать 'foreach (задача var в completeTasks, где task.IsFaulted или task.IsCanceled)', поэтому вам не нужно проверять завершенные задачи. конечно, непроверенный. – Nzall

+0

@NateKerkhofs Оставим эту часть обработки ошибок OP, он не дал нам понять, как ему нужно справляться с этим сбоем. –

Смежные вопросы