2016-02-11 5 views
3

Мое требование довольно странное.Выполнять несколько экземпляров одного и того же метода асинхронно?

У меня есть SomeMethod(), который вызывает GetDataFor().

public void SomeMethod() 
{ 
    for(int i = 0; i<100; i++) { 
     var data = GetDataFor(i); 
    } 
} 

public data GetDataFor(int i) { 
    //call a remote API 
    //to generate data for i 
    //store to database 
    return data; 
} 

Для каждого i, конечный результат будет всегда быть разными. Нет необходимости подождать за GetDataFor(i) для заполнения до звонка GetDataFor(i+1).

Другими словами, мне нужно:

  • вызова GetDataFor() для каждого i+1сразу после успешного вызова i (Calling их параллельно выглядит невозможным)
  • ждать пока все 100 экземпляров GetDataFor() завершены работа
  • оставить в поле зрения SomeMethod()

После YK1's answer, я попытался изменить это так:

public async Task<void> SomeMethod() 
{ 
    for(int i = 0; i < 100; i++) { 
     var task = Task.Run(() => GetDataFor(i)); 
     var data = await task; 
    } 
} 

Он не бросил какие-либо ошибки, но мне нужно понять концепцию за этим:

  • как будет task различать разные вызовы для await ing? Он переписывается.
  • Наверно, это неправильный способ сделать это? Итак, как все правильно?
+4

Ваш код не запускает их параллельно. Они запускаются один за другим, так как вы продолжаете ждать завершения. – poke

+0

@poke Получил ваш момент. Я тоже не уверен. Как мы это исправим? – xameeramir

+2

Что является основным компонентом 'GetDataFor'? Связано ли это с CPU? Диск связан? Сеть связана? В зависимости от ответа, вы, возможно, не захотите запускать их параллельно. –

ответ

5

Вы можете использовать Parallel.For:

public void SomeMethod() 
{ 
    Parallel.For(0, 100, i => 
    { 
     var data = GetDataFor(i); 
     //Do something 
    }); 
} 

public data GetDataFor(int i) 
{ 
    //generate data for i 
    return data; 
} 

EDIT:

Синтаксис параллельного цикла очень похожа на for и foreach петель, вы уже знаете, но параллельный цикл выполняется быстрее компьютер с имеющимися ядрами. Другое отличие состоит в том, что в отличие от последовательного цикла порядок выполнения не определен для параллельного цикла. Шаги часто происходят одновременно, параллельно. Иногда два шага происходят в противоположном порядке, чем если бы цикл был последовательным. Единственная гарантия заключается в том, что все итерации цикла будут выполняться к завершению цикла.

Для параллельных циклов степень параллелизма не требуется указывать вашим кодом. Вместо этого среда выполнения выполняет шаги цикла в одно и то же время на столько ядер, сколько может. Цикл работает правильно независимо от того, сколько ядер доступно. Если есть только одно ядро, производительность близка (в пределах нескольких процентных пунктов) к последовательному эквиваленту. Если имеется несколько ядер, производительность улучшается; во многих случаях производительность улучшается пропорционально количеству ядер.

Вы можете увидеть более подробное объяснение here.

+1

Хороший призыв к использованию параллелизма, ОП упомянул в то же время. –

+2

Это будет работать только параллельно, исходя из количества ядер, которые у вас есть. Если у вас 1 ядро, то ничего не улучшится. – sr28

+0

@ sr28 Не могли бы вы объяснить, почему он не будет выполняться параллельно? – xameeramir

1

Код на самом деле не имеет смысла.

Как будет различать различные призывы к ожиданию? Он получает переписанный.

Не переписывается. Потому что ...

for(int i = 0; i < 100; i++) { 
    var task = Task.Run(() => GetDataFor(i)); 
    var data = await task; 
} 

Это ОЖИДАНИЕ для каждого запроса до завершения цикла. Ожидание ждет конца.

Это означает, что вся задача не имеет значения - здесь ничего не происходит. Вы можете сократить некоторые незначительные накладные расходы, выполнив это без задачи.

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

+2

И ваш ответ на вопрос («как запустить это параллельно») является…? – poke

+0

@TomTom - Я обновлю свой вопрос, чтобы решить эту проблему. – xameeramir

2

Я бы вместо этого добавлял каждую из задач в коллекцию, а затем ожидал всю коллекцию ПОСЛЕ цикла.

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

Посмотрите на ожидаемое Task.WaitAll.

Если значение важности каждой задачи важно для обработки, то посмотрите на ожидающий Task.WhenAll, а затем прочитайте результаты каждой задачи в своей возвратной коллекции.

+0

@student, действительно ли возвращает данные GetDataFor? –

+0

Да, он возвращает 'data' – xameeramir

+1

Я добавил случай использования WhenAll, который будет обрабатывать этот сценарий, но у Артуро-Менчака может быть лучший случай для вас в зависимости от того, хотите ли вы, чтобы код TRULY работал бок о бок или просто не был, блокировка асинхронная. –

0

Хотя ваш исходный код перезаписывает значения, кажется, что вы пытаетесь совместить результаты параллельных операций. Если да, рассмотрите возможность использования Task.ContinueWith для обработки возвращаемых значений. Ваш код будет выглядеть примерно так:

public void SomeMethod() 
    List<Task> tasks = new List<Task>(); 
    for (var i = 0; i < 100; i++) 
    { 
     tasks.Add(Task.Run(() => GetDataFor(i)).ContinueWith((antecedent) => { 
      // Process the results here. 
     })); 
    } 
    Task.WaitAll(tasks.ToArray()); 
} 
+0

Это ничем не отличается от использования ключевого слова ожидания. –

+0

@OvanCrone Это неправда. Все задачи будут выполняться асинхронно. OP сказал, что GetDataFor разрешено асинхронно. Task.WaitAll предотвращает возврат SomeMethod до тех пор, пока все не будут завершены, если это то, что вы имеете в виду, но OP не сказал, что содержащий метод должен быть асинхронным. –

+0

@OvanCrone Если у вас есть минута, чтобы расширить свой комментарий, я был бы признателен, особенно после downvote :-) Я запустил свой код в простом приложении, чтобы проверить, что все задачи GetDataFor и все задачи ContinueWith выполняются асинхронно и в параллельно; нет необходимости ждать завершения одной задачи до появления другого. Если я чего-то упускаю, я открыт для конструктивной критики! –

2

Существует несколько разных подходов.

Во-первых, вы можете держать его синхронным и просто выполнять их параллельно (на разных потоках). Parallel LINQ лучше Parallelесли вы хотите, чтобы собрать все результаты в вызывающем методе перед тем продолжает:

public data[] SomeMethod() 
{ 
    return Enumerable.Range(0, 100) 
     .AsParallel().AsOrdered() 
     .Select(GetDataFor).ToArray(); 
} 

Во-вторых, вы можете сделать это асинхронно. Чтобы сделать что-то по-настоящему асинхронным, вам нужно начать с самого низкого уровня (в этом случае «вызвать удаленный API» и «сохранить в базе данных») и сделать это асинхронным первым.После этого вы можете сделать GetDataFor асинхронно:

public async Task<data> GetDataForAsync(int i) 
{ 
    await .. //call a remote API asynchronously 
    await .. //store to database asynchronously 
    return data; 
} 

Затем вы можете сделать SomeMethod асинхронный, а также:

public Task<data[]> SomeMethodAsync() 
{ 
    return Task.WhenAll(
     Enumerable.Range(0, 100).Select(GetDataForAsync) 
); 
} 

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

2

При использовании asyncawait вы, по сути, говорите: «В ожидании завершения этой задачи, пожалуйста, уйдите и выполните самостоятельную работу, которая не полагается на эту задачу». Поскольку вы не заботитесь о том, чтобы ждать GetDataFor, вы не хотите использовать asyncawait.

This previous question похоже, имеет очень похожий запрос, как ваш. Имея это в виду, я думаю, вы должны быть в состоянии сделать что-то вроде этого:

public void SomeMethod() 
{ 
    Task.Run(() => GetDataFor(i)); 
} 

В основном, это предполагает, что вам не нужно ждать для GetDataFor закончить, прежде чем делать что-нибудь еще, это буквально «огонь и забыть ».

Что касается Parallel.For, вы, вероятно, увидите некоторое улучшение производительности, если у вас более 1 ядра. Если нет, вы, вероятно, увидите столь незначительное снижение производительности (больше накладных расходов). Here's an article, который помогает объяснить, как это работает.

UPDATE

После вашего комментария, то я хотел бы предложить что-то вроде:

var TList = new List<Task>(); 

for (var i = 0; i < 100; i++) 
{ 
    TList.Add(Task.Run(() => GetDataFor(i))); 
} 

await Task.WhenAll(TList);  

Here's a useful question, что выдвигает на первый план, почему вы можете захотеть использовать WhenAll вместо WaitAll.

Возможно, вы захотите включить некоторую проверку состояния завершения задач, чтобы увидеть, какой сбой (если есть). См. Пример here.

+0

Мне нужно подождать, пока все 'GetDataFor()' будут завершены. – xameeramir

+0

Кроме того, он делает вещи тяжелыми? Моя визуальная студия не может справиться с F5, и я должен ее убить. – xameeramir

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