Страница 441 из 502
Ниже приведена форма объявления метода WithCancellation():
<b>public static ParallelQuery<TSource> WithCancellation<TSource> (</b>
<b> this ParallelQuery<TSource> source,</b>
<b> CancellationToken cancellationToken)</b>
где source обозначает вызывающий запрос, a cancellationToken — признак отмены. Этот метод возвращает запрос, поддерживающий указанный признак отмены.
В приведенном ниже примере программы демонстрируется порядок отмены параллельного запроса, сформированного в программе из предыдущего примера. В данной программе организуется отдельная задача, которая ожидает в течение 100 миллисекунд, а затем отменяет запрос. Отдельная задача требуется потому, что цикл foreach, в котором выполняется запрос, блокирует выполнение метода Main() до завершения цикла.
<b>// Отменить паралельный запрос</b>
<b>using System;</b>
<b>using System.Linq; </b>
<b>using System.Threading; </b>
<b>using System.Threading.Tasks; </b>
<b>class PLINQCancelDemo { </b>
<b> static void Main() { </b>
<b> CancellationTokenSource cancelTokSrc = new CancellationTokenSource(); </b>
<b> int[] data = new int[10000000]; </b>
<b> // Инициализировать массив данных положительными значениями, </b>
<b> for (int i=0; i < data.Length; i++) data[i] = i; </b>
<b> //А теперь ввести в массив данных ряд отрицательных значений, </b>
<b> data[1000] = -1; </b>
<b> data [14000] = -2; </b>
<b> data[15000] = -3; </b>
<b> data[676000] = -4; </b>
<b> data[8024540] = -5; </b>
<b> data [9908000] = -6; </b>
<b> // Использовать запрос PLINQ для поиска отрицательных значений, </b>
<b> var negatives = from val in </b>
<b> data.AsParallel(). WithCancellation(cancelTokSrc.Token) </b>
<b> where val < 0 </b>
<b> select val; </b>
<b> // Создать задачу для отмены запроса по истечении 100 миллисекунд. </b>
<b> Task cancelTsk = Task.Factory.StartNew(() => { </b>
<b> Thread.Sleep(100); </b>
<b> cancelTokSrc.Cancel(); </b>
<b> }); </b>
<b> try { </b>
<b> foreach(var v in negatives) </b>
<b> Console.Write(v + " "); </b>
<b> } catch(OperationCanceledException exc) { </b>
<b> Console.WriteLine(exc.Message); </b>
<b> } catch(AggregateException exc) { </b>
<b> Console.WriteLine (exc); </b>
<b> } finally { </b>
<b> cancelTsk.Wait(); </b>
<b> cancelTokSrc.Dispose(); </b>
<b> cancelTsk.Dispose(); </b>
<b> } </b>
<b> Console.WriteLine(); </b>
<b> }</b>
<b>}</b>
Ниже приведен результат выполнения этой программы. Если запрос отменяется до его завершения, то на экран выводится только сообщение об исключительной ситуации.
<b>Запрос отменен с помощью маркера, переданного в метод WithCancellation.</b>
Как упоминалось ранее, PLINQ представляет собой довольно крупную подсистему. Это объясняется отчасти той гибкостью, которой обладает PLINQ. В PLINQ доступны и многие другие средства, помогающие подстраивать параллельные запросы под конкретную ситуацию. Так, при вызове метода WithDegreeOfParallelism() можно указать максимальное количество процессоров, выделяемых для обработки запроса, а при вызове метода AsSequential() — запросить последовательное выполнение части параллельного запроса. Если вызывающий поток, ожидающий результатов от цикла foreach, не требуется блокировать, то для этой цели можно воспользоваться методом ForAll(). Все эти методы определены в классе ParallelEnumerable. А в тех случаях, когда PLINQ должен по умолчанию поддерживать последовательное выполнение, можно воспользоваться методом WithExecutionMode(), передав ему в качестве параметра признак ParallelExecutionMode.ForceParallelism.
Далеко не все запросы выполняются быстрее только потому, что они распараллелены. Как пояснялось ранее в отношении TPL, издержки, связанные с созданием параллельных потоков и управлением их исполнением, могут "перекрыть" все преимущества, которые дает распараллеливание. Вообще говоря, если источник данных оказывается довольно мелким, а требующаяся обработка данных — очень короткой, то внедрение параллелизма может и не привести к ускорению обработки запроса. Поэтому за рекомендациями по данному вопросу следует обращаться к информации корпорации Microsoft.