5

Я создаю приложение WP8, и мне нужно выполнить около 30 веб-запросов. Эти запросы не зависят друг от друга, поэтому они могут быть распараллелены.Как выполнять параллельные задачи на Windows Phone?

Мой код выглядит следующим образом (упрощенный/псевдокод):

foreach (Uri uri in uris) 
{ 
    var rawData = await Task.Run(() => httpClient.GetStringAsync(uri).ConfigureAwait(false)); 

    var processedData = dataProcessor.Process(rawData); 
    processedDataCollection.Add(processedData); 
} 

Когда я смотрю на скрипача, запросы выполняются все sequantial; для выполнения и обработки всех них требуется несколько секунд. Тем не менее, я не хочу, чтобы код дождался, пока 1 запрос не будет закончен, прежде чем перейти к следующему, я хочу выполнить несколько запросов одновременно.

Обычно я хотел бы использовать Parallel.Invoke() или Parallel.ForEach() или что-то подобное, чтобы сделать это, но, видимо, параллельная библиотека не доступна в Windows Phone 8.

Так что самый лучший способ для достижения этой цели? Task.Factory.StartNew()? new Thread()?

ответ

10

Нет необходимости запускать каждый запрос в отдельном потоке. Вы можете так же легко сделать это:

var raw = await Task.WhenAll(uris.Select(uri => httpClient.GetStringAsync(uri))); 
var processed = raw.Select(data => dataProcessor.Process(data)).ToArray(); 

Этот код берет коллекцию uris и запускает HTTP загрузки для каждого из них (Select(...)). Затем он асинхронно ждет их завершения (WhenAll). Затем все данные обрабатываются во второй строке кода.

Однако, скорее всего, время выполнения Windows Phone ограничит максимальное количество запросов, которые могут быть у вас на одном сервере.

+0

Мне очень нравится этот. Очень компактный и элегантный. Я не мог заставить его работать с другим кодом, но это работает отлично. Ваше сообщение было бы немного более полезным, если бы вы могли объяснить шаги и почему это действительно работает. –

+1

Обновлено, чтобы включить больше объяснений. –

3

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

Вы можете сделать этот запуск так, как вы (вероятно) намерены использовать метод Task.WaitAll. Что-то вроде этого должно хватить:

var tasks = new List<Task>(); 

foreach (Uri uri in uris) 
{ 
    var thisTask = Task.Run(() => 
         { 
         httpClient.GetStringAsync(uri).ConfigureAwait(false); 
         var processedData = dataProcessor.Process(rawData); 
         processedDataCollection.Add(processedData); 
         }); 

    tasks.Add(thisTask); 
} 

Task.WaitAll(tasks); 

Обратите внимание, что это также параллелизует обработку данных (то есть, он будет вызывать .Process параллельно также), что исходный код не сделал.

Я бы также добавил, что вы, вероятно, не хотите хотите запустить сразу 30 запросов, если только они не предназначены для разных серверов. Выполнение 30 одновременных запросов к Uris на том же сервере вряд ли улучшит ваше время обработки. (К счастью, TPL, скорее всего, позаботится об этом для вас, поскольку это немного ограничит параллелизм.)

+0

Что возвращает GetStringAsync() в вашем примере? Я могу получить его, чтобы вернуть «Задача » или «ConfiguredTaskAwaitable ». Как использовать это для метода Process()? Нужно ли мне вызывать '.Result' метод' GetStringAsync'? –

+0

И не лучше ли использовать 'wait Task.WhenAll' вместо' Task.WaitAll'? Или у них разные результаты? –

+0

'Task.WhenAll' создает новую задачу, которая не будет завершена, пока все остальные не закончатся; 'Task.WaitAll' просто ждет их завершения. Если вам все равно, когда они завершатся и могут продолжаться независимо, вы можете полностью опустить этот вызов. Что касается 'GetStringAsync' ... –