ParallelEnumerable 类
- 包含在
System.Linq
命名空间中 - 分解查询工作,使其分布在多个线程上
- 查询是分区的,以便多个线程可以同时处理该查询,分区的工作各自完成后,需要合并
- 它的大多数扩展方法,是
ParallelQuery<TSource>
类的扩展方法
AsParallel() 方法
- 在
ParallelEnumerable
类中定义 - 它是
IEnumerable<TSource>
的扩展方法- 因此,正常的集合类都可以以并行方式查询
- 它也是
Partitioner
类的扩展方法,可以影响要创建的分区 - 它返回
ParallelQuery<TSource>
类- 在此基础上调用的
Where()
,不是Enumerable.Where()
- 而是
ParallelEnumerable.Where()
- 在此基础上调用的
- 观察
AsParallel()
生效的方式: 系统所有CPU都在忙碌。如果去掉它,就不可能使用多个CPU
并行查询
对于可以放在CPU缓存中的小集合,并行LINQ看不出效果
对于没有多个CPU的系统,并行LINQ也没有效果 先构造一个大集合:
static IList<int> SampleData()
{
const int arraySize = 50000000;
var r = new Random();
return Enumerable.Range(0, arraySize).Select(x => r.Next(140)).ToList();
}
并行查询,使用AsParallel()
方法:
- 和子句语法的LINQ查询一样,编译器会修改它,以实际调用
ParallelEnumerable
的AsParallel()
ParallelEnumerable
的Where()
ParallelEnumerable
的Select()
ParallelEnumerable
的Average()
static void LinqQuery(IEnumerable<int> data)
{
var res = (from x in data.AsParallel()
where Math.Log(x) < 4
select x).Average();
}
static void ExtensionMethods(IEnumerable<int> data)
{
var res = data.AsParallel()
.Where(x => Math.Log(x) < 4)
.Select(x => x).Average();
}
分区器: Partitioner 类
- 在
System.Collection.Concurrent
命名空间 - 它的
Create()
方法,接收实现了IList<T>
的类型实例
static void UseAPartitioner(IList<int> data)
{
var res = (from x in Partitioner.Create(data, loadBalance: true).AsParallel()
where Math.Log(x) < 4
select x).Average();
}
数组专用的分区器:
DynamicPartitionerForArray<TSource>
和StaticPartitionerForArray<TSource>
- 它们继承自抽象基类
OrderablePartitioner<TSource>
- 它们继承自抽象基类
其他方法影响并行机制:
WithExecutionMode()
- 可以传递一个
ParallelExecutionMode
的默认值。(默认情况下,并行LINQ避免使用系统开销很高的并行机制) - 或者,可以传递
ForceParallelism
值
- 可以传递一个
WithDegreeOfParallelism()
- 可以传递一个整数值,指定并行运行的最大任务数
- 当查询不应该使用全部CPU时,这个方法会很有用
取消
要取消长时间运行的任务,可以给查询添加
WithCancellation()
方法,并传递一个CancellationToken
令牌作为参数
CancellationToken
从CancellationTokenSource
中创建- 当要取消任务时:
- 主线程中,调用
CancellationTokenSource
类的Cancel()
方法 - 在查询运行的线程中,查询会中止,然后抛一个
OperationCanceledException
异常。我们需要捕获这个异常
- 主线程中,调用
static void UseCancellation(IEnumerable<int> data)
{
Console.WriteLine(nameof(UseCancellation));
var cts = new CancellationTokenSource();
Task.Run(() =>
{
try
{
var res = (from x in data.AsParallel().WithCancellation(cts.Token)
where Math.Log(x) < 4
select x).Average();
Console.WriteLine($"query finished, sum: {res}");
}
catch (OperationCanceledException ex)
{
Console.WriteLine(ex.Message);
}
});
Console.WriteLine("query started");
Console.Write("cancel? ");
string input = Console.ReadLine();
if (input.ToLower().Equals("y"))
{
cts.Cancel();
}
Console.WriteLine();
}