Parallel Linq - вернуть первый результат, который возвращается - PullRequest
2 голосов
/ 12 ноября 2011

Я использую PLINQ для запуска функции, которая проверяет последовательные порты, чтобы определить, являются ли они устройством GPS.

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

Могу ли я сделать это с помощью PLINQ или мне нужно запланировать пакет задач и дождаться его возврата?

Ответы [ 4 ]

6 голосов
/ 12 ноября 2011

PLINQ, вероятно, здесь будет недостаточно.Хотя вы можете использовать .First, в .NET 4 это приведет к его последовательной работе, что противоречит цели.(Обратите внимание, что это будет улучшено в .NET 4.5 .)

Однако TPL, скорее всего, является правильным ответом здесь.Вы можете создать Task<Location> для каждого последовательного порта, а затем использовать Task.WaitAny для ожидания первой успешной операции.

Это обеспечивает простой способ запланировать кучу «задач».", а затем просто используйте первый результат.

0 голосов
/ 06 ноября 2017

Для достижения цели полностью с PLINQ в .NET 4.0:

SerialPorts.                        // Your IEnumerable of serial ports
    AsParallel().AsUnordered().     // Run as an unordered parallel query
    Where(IsGps).                   // Matching the predicate IsGps (Func<SerialPort, bool>)
    Take(1).                        // Taking the first match
    FirstOrDefault();               // And unwrap it from the IEnumerable (or null if none are found

Ключ заключается в том, чтобы не использовать упорядоченную оценку, такую ​​как First или FirstOrDefault, пока вы не укажете, что вам нужна только одна.

0 голосов
/ 10 октября 2013

Я думал об этом время от времени в течение последних нескольких дней, и я не могу найти встроенный PLINQ способ сделать это в C # 4.0. Принятый ответ на этот вопрос об использовании FirstOrDefault не возвращает значение, пока не завершится полный запрос PLINQ, и все еще возвращает (упорядоченный) первый результат. В следующем крайнем примере показано поведение:

var cts = new CancellationTokenSource();
var rnd = new ThreadLocal<Random>(() => new Random());

var q = Enumerable.Range(0, 11).Select(x => x).AsParallel()
    .WithCancellation(cts.Token).WithMergeOptions( ParallelMergeOptions.NotBuffered).WithDegreeOfParallelism(10).AsUnordered()
    .Where(i => i % 2 == 0 )
    .Select( i =>
    {
        if( i == 0 )
            Thread.Sleep(3000);
        else
            Thread.Sleep(rnd.Value.Next(50, 100));
        return string.Format("dat {0}", i).Dump();
    });

cts.CancelAfter(5000);

// waits until all results are in, then returns first
q.FirstOrDefault().Dump("result");

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

Первый создает Задачи для выполнения работы и возвращает Задачу, что приводит к быстрому выполнению запроса PLINQ. Результирующие задачи могут быть переданы в WaitAny для получения первого результата, как только он станет доступен:

var cts = new CancellationTokenSource();
var rnd = new ThreadLocal<Random>(() => new Random());

var q = Enumerable.Range(0, 11).Select(x => x).AsParallel()
    .WithCancellation(cts.Token).WithMergeOptions( ParallelMergeOptions.NotBuffered).WithDegreeOfParallelism(10).AsUnordered()
    .Where(i => i % 2 == 0 )
    .Select( i =>
    {
        return Task.Factory.StartNew(() =>
        {
        if( i == 0 )
            Thread.Sleep(3000);
        else
            Thread.Sleep(rnd.Value.Next(50, 100));
        return string.Format("dat {0}", i).Dump();
        });
    });

cts.CancelAfter(5000);

// returns as soon as the tasks are created
var ts = q.ToArray();

// wait till the first task finishes
var idx = Task.WaitAny( ts );
ts[idx].Result.Dump("res");

Это, наверное, ужасный способ сделать это. Поскольку фактическая работа запроса PLINQ - это очень быстрая задача Task.Factory.StartNew, бессмысленно использовать PLINQ вообще. Простой .Select( i => Task.Factory.StartNew( ... в IEnumerable чище и, вероятно, быстрее.

Второй обходной путь использует очередь (BlockingCollection) и просто вставляет результаты в эту очередь после их вычисления:

var cts = new CancellationTokenSource();
var rnd = new ThreadLocal<Random>(() => new Random());

var q = Enumerable.Range(0, 11).Select(x => x).AsParallel()
    .WithCancellation(cts.Token).WithMergeOptions( ParallelMergeOptions.NotBuffered).WithDegreeOfParallelism(10).AsUnordered()
    .Where(i => i % 2 == 0 )
    .Select( i =>
    {
        if( i == 0 )
            Thread.Sleep(3000);
        else
            Thread.Sleep(rnd.Value.Next(50, 100));
        return string.Format("dat {0}", i).Dump();
    });

cts.CancelAfter(5000);

var qu = new BlockingCollection<string>();

// ForAll blocks until PLINQ query is complete
Task.Factory.StartNew(() => q.ForAll( x => qu.Add(x) ));

// get first result asap
qu.Take().Dump("result");

С помощью этого метода работа выполняется с использованием PLINQ, а функция Take () BlockingCollecion вернет первый результат, как только он будет вставлен в запрос PLINQ.

Хотя это приводит к желаемому результату, я не уверен, что он имеет какое-либо преимущество перед простым использованием простых задач + WaitAny

0 голосов
/ 14 ноября 2011

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

http://msdn.microsoft.com/en-us/library/dd460677.aspx

...