Parallel.Foreach c # Функция паузы и остановки? - PullRequest
5 голосов
/ 11 декабря 2011

Какой самый эффективный способ приостановить и остановить (до того, как он закончится) параллельно.foreach?

Parallel.ForEach(list, (item) =>
{
    doStuff(item);
});

Ответы [ 2 ]

11 голосов
/ 11 декабря 2011

Damien_The_Unbeliver имеет хороший метод, но это только если вы хотите, чтобы какой-то внешний процесс остановил цикл. Если вы хотите, чтобы цикл прерывался, как при использовании break в обычном цикле for или foreach, вам нужно будет использовать перегрузку с ParallelLoopState как один из параметров тела цикла. ParallelLoopState имеет две функции, которые относятся к тому, что вы хотите сделать, Stop() и Break().

Функция Stop() остановит обработку элементов при самом раннем удобстве системы , что означает, что после вызова Stop () может быть выполнено больше итераций, и не гарантируется, что элементы, предшествовавшие элементу, будут остановились даже начали процесс.

Функция Break() выполняет в точности то же самое, что и Stop(), однако она также оценивает все элементы IEnumerable, которые были до элемента, который вы называли Break(). Это полезно, когда вам все равно, в каком порядке обрабатываются элементы, но вы должны обработать все элементы до того момента, когда вы остановились.

Проверьте ParallelLoopResult , возвращенный из foreach, чтобы увидеть, остановился ли foreach раньше, и если вы использовали Break(), какой элемент с наименьшим номером он обработал.

Parallel.ForEach(list, (item, loopState) =>
    {
        bool endEarly = doStuff(item);
        if(endEarly)
        {
            loopState.Break();
        }
    }
    );
//Equivalent to the following non parallel version, except that if doStuff ends early
//    it may or may not processed some items in the list after the break.
foreach(var item in list)
{
    bool endEarly = doStuff(item);
    if(endEarly)
    {
        break;
    }
}

Вот более практичный пример

static bool[] list = new int[]{false, false, true, false, true, false};

long LowestElementTrue()
{
    ParallelLoopResult result = Parallel.ForEach(list, (element, loopState) =>
    {
        if(element)
            loopState.Break();
    }
    if(result.LowestBreakIteration.IsNull)
        return -1;
    else
        return result.LowestBreakIteration.Value;
}   

Независимо от того, как это разделит работу, он всегда вернет 2 в качестве ответа.

скажем, процессор отправляет два потока для обработки этого, первый поток обрабатывает элементы 0-2, а второй поток обрабатывает элементы 3-5.

Thread 1:                Thread 2
0, False, continue next  3, False, continue next
1, False, continue next  4, True, Break
2, True, Break           5, Don't process Broke

Теперь был вызван самый низкий индекс Break, равный 2, поэтому ParallelLoopResult.LowestBreakIteration будет возвращать 2 каждый раз, независимо от того, как будут разбиты потоки, поскольку он всегда будет обрабатываться до числа 2.

Вот пример того, как можно использовать Stop.

static bool[] list = new int[]{false, false, true,  false, true, false};

long FirstElementFoundTrue()
{
    long currentIndex = -1;
    ParallelLoopResult result = Parallel.ForEach(list, (element, loopState, index) =>
    {
        if(element)
        {
             loopState.Stop();

             //index is a 64 bit number, to make it a atomic write
             // on 32 bit machines you must either:
             //   1. Target 64 bit only and not allow 32 bit machines.
             //   2. Cast the number to 32 bit.
             //   3. Use one of the Interlocked methods.
             Interlocked.Exchange (ref currentIndex , index);
        }
    }
    return currentIndex;
}   

В зависимости от того, как он разделит работу, он вернет 2 или 4 в качестве ответа.

скажем, процессор отправляет два потока для обработки этого, первый поток обрабатывает элементы 0-2, а второй поток обрабатывает элементы 3-5.

Thread 1:                 Thread 2
0, False, continue next    3, False, continue next
1, False, continue next    4, True, Stop
2, Don't process, Stopped  5, Don't process, Stopped

в этом случае он вернет 4 в качестве ответа. Давайте посмотрим на тот же процесс, но если он обрабатывает все остальные элементы вместо 0-2 и 3-5.

Thread 1:                   Thread 2
0, False, continue next     1, False, continue next
2, True, Stop               3, False, continue next
4, Don't process, Stopped   5, Don't process, Stopped

На этот раз он вернет 2 вместо 4.

2 голосов
/ 11 декабря 2011

Чтобы иметь возможность остановить Parallel.ForEach, вы можете использовать одну из перегрузок, которая принимает параметр ParallelOptions, и включить в эти опции CancellationToken.

Подробнее см. Отмена .

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

...