2015-04-22 3 views
4

У меня есть программа, интенсивно использующая вычислительные ресурсы, которую я пытаюсь распараллелить, однако одним из предельных шагов является операция ввода-вывода, которая контролируется феноменально неэффективным API. Я не контролирую, но у меня нет выбора, кроме как использовать. Крайне важно, чтобы мое распараллеливание не увеличивало число операций ввода-вывода, или любая выгода, скорее всего, очень быстро исчезнет.Каково поведение ожидания внутри цикла Parallel.ForEach()?

Раскладка что-то вроде этого: У меня есть два класса, Foo и Bar, и для того, чтобы рассчитать Foo, который не включает в себя не малое количество вычислений, я должен передать его экземпляр или несколько экземпляров, из Bar которых Я импортирую из другого файла в чрезвычайно дорогостоящую операцию ввода-вывода. Я требую большого количества экземпляров как , так и Bar. Многие из этих Bar экземпляров будут использованы для расчета более чем одного экземпляра Foo. В результате я не хочу отбрасывать свои экземпляры Bar после того, как я вычислил каждый номер Foo, и я не хочу импортировать их более одного раза. Потенциально замечать, что сложнее усложнять API, 32-разрядный, тогда как моя программа должна быть 64-битной, чтобы избежать MemoryException, поэтому она обрабатывается локально размещенным сервером, с которым я общаюсь с использованием WCF.

Вот предложили мое решение, но я очень новый для распараллеливания и, в частности, я не уверен в том, как await будет обрабатываться внутри WRT петли ForEach высвобождая процессоры:

ConcurrentDictionary<string, Task<Bar>> barList = new ConcurrentDictionary<string, Task<Bar>>(); 

Parallel.ForEach(fooList, foo => 
{ 
    if (!barList.ContainsKey(this.RequiredBarName)) 
    { 
     Task<Bar> importBar = Task.Run(() => Import.BarByName(this.RequiredBarName)); 
     barList.Add(this.RequiredBarName,importBar); 
    } 
    this.RequiredBarTask = barList.TryGetValue(this.RequiredBarName); 
    foo.CalculateStuff(); 
} 

// where foo.CalculateStuff() looks something like this 
async public void CalculateStuff() 
{ 
    // do some stuff... 
    Bar requiredBar = await this.RequiredBarTask; 
    // do some more stuff with requiredBar 
} 

Что будет когда код работает в этом await? Будет ли ThreadPool брать другой Task, или процессор просто простаивает? Если я тогда устрою своего рода WaitAll() за пределами Parallel.ForEach(), смогу ли я распараллелить все это эффективно? Есть ли у кого-нибудь лучшие идеи о том, как я могу это реализовать?

Редактировать, чтобы обеспечить MCVE:

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

Программа может эффективно идти бесконечно глубоко в том, как она обрабатывает вещи, ее гораздо легче воспринимать как синтаксический анализатор конкретных инструкций, которые клиенту разрешено строить с помощью графического интерфейса и набора «кирпичей». Таким образом, Dataflow выглядит так, что может предложить достойное решение.

В этом примере я не заботиться о циклических ссылках или одного Channel вычисления другого Channel который уже был вызван по методу Parallel.ForEach(); в моем коде это обрабатывается некоторыми логическими и параллельными списками, чтобы проверять, когда были вызваны различные вещи.

public abstract class Class 
{ 
    public string Name {get;set;} 
    public float[] Data {get;set;} 

    async public Task CalculateData(IsampleService proxy){} 
} 

public class Channel : Class 
{ 
    public Class[] ChildClasses {get;set;} 

    async public override Task CalculateData(IsampleService proxy) 
    { 
     foreach(Class childClass in ChildClasses) 
     { 
      // not the real processing but this step could be anything. There is a class to handle what happens here, but it is unnecessary for this post. 
      if(childClass.Data==null) await childClass.CalculateData(proxy); 
      this.Data = childClass.Data; 
     } 
    } 
} 

public class Input : Class 
{ 
    async public override Task CalculateData(IsampleService proxy) 
    { 
      this.Data = await proxy.ReturnData(this.Name); 
    } 
} 

async public static Task ProcessDataForExport(Channel[] channelArray) 
{ 
ChannelFactory<IsampleService> factory = new ChannelFactory<IsampleService>(new NetNamedPipeBinding(), new EndpointAddress(baseAddress)); 

IsampleService proxy = factory.CreateChannel(); 

Parallel.ForEach(channelArray, channel => 
    { 
     channel.CalculateData(); 
    }); 
// Task.WhenAll() might be a better alternative to the Parallel.ForEach() here. 
} 
+2

Никогда, никогда, пользователь 'async void', если вы не создаете обработчик событий. Вы можете сделать «async foo => ...», а затем «async public Task CalculateStuff()» и «ждать foo.CalculateStuff()», но я не уверен, что это правильная вещь. –

+1

Вот какой контекст, почему следует избегать async void https://msdn.microsoft.com/en-us/magazine/jj991977.aspx – rashleighp

+0

Спасибо вам, да, предложение использовать «async Task» является хорошим и сделало бы обработку всего этого чище, когда я в конечном итоге обработаю его в методе 'Main()' Консоли, но он не отвечает на вопрос, на который я ответил. Мне не нужно «ждать» foo.CalculateStuff() ', потому что мне не нужен результат, прежде чем продолжить мой код, но опять же, это не имеет отношения к вопросу: процессор будет заблокирован' await' внутри 'CalculateStuff()' или будет цикл 'ForEach()' попробует другой 'Foo' и посмотреть, готов ли он' requiredBar' к доступу? – thepowerofnone

ответ

2

Что произойдет, когда код выполняется в том, что ждать?

То же самое, что происходит из-за любой await заявления: после оценки независимо выражения или оператор извлекает Task к ожидаться, этот метод будет возвращать. - конец метода.

Будет ли ThreadPool выполнять другую задачу или процессор просто простаивает?

Это зависит от того, что еще происходит. Например, что вы ждете? Если это вычислительная задача, поставленная в очередь на пул потоков, и она еще не была назначена нить пула потоков, то sure & hellip; пул потоков может забрать это и начать работать над ним.

Если вы ожидаете операции ввода-вывода, это не обязательно приведет к перегрузке процессора, но в очереди пула потоков могут остаться другие задачи (например, другие из вызова Parallel.ForEach()). Так что это даст процессору что-то работать.

Конечно, использование await обычно не приводит к простоям процессинга. На самом деле, основная причина его использования заключается в том, чтобы избежать этого (*). Поскольку оператор await заставляет текущий метод возвращаться, вы пропускаете текущий поток, а это означает, что если в противном случае было недостаточно потоков, чтобы заняться процессором, теперь ему нужно что-то делать. :)

(*) (ну, вроде, и действительно, главная причина - не блокировать текущую нить, но имеет побочный эффект от того, что обработчик обрабатывает больше работы :))

Если я затем устрою какой-то WaitAll() вне Parallel.ForEach(), я смогу распараллеливать все это эффективно? Есть ли у кого-нибудь лучшие идеи о том, как я могу это реализовать?

Я не вижу достаточно полезной детали в вашем вопросе, чтобы ответить на это. Честно говоря, пока я не могу наложить на него свой палец, использование await делегата Parallel.ForEach() кажется мне каким-то подозрительным. Как только вы вызовете await, метод делегата вернется.

Следовательно, поскольку Parallel.ForEach() знает, что вы закончили с этим элементом в перечислении, но, конечно же, вы этого не сделали. Его нужно будет закончить в другом месте. По крайней мере, похоже, что это помешало бы способности класса Parallel достаточно хорошо узнать о работе, которую он делает, чтобы запланировать ее наиболее эффективно.

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


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

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