C# 异步编程(并行循环)
并行循环
本节将简要介绍任务并行库(Task Parellel Library)。它是BCL中的一个类库,极大地简化了
并行编程。本章无法尽述其细节,这里只介绍其中两个简单的结构,你可以轻松快速地掌握并用,它们是Parallel.For循环和ParalleI.ForEach循环。这两个结构位于system.Threading.Tasks命名
空间中。
至此,相信你应该很熟悉c#的标准for和foreach循环了。这两个结构非常普遍,且极其强
大。许多时候,在使用这两个结构时,每一次迭代都依赖于前一次迭代的计算或行为。但有的时
候又不是这样。如果迭代之间彼此独立,并且程序运行在多处理器机器上,那么若能将不同的迭
代放在不同的处理器上并行处理的话,将会获益匪浅。Parallel.For和Parallel.ForEach结构就
是这样做的。
这两个结构的形式是包含输人参数的方法。Parallel.For方法有12个重载,其中最简单的
那个的签名如下。
publicstaticParallelLoopResult.(int fromInclusive,int toExclusive,Action body)
- fromlnclusive参数是迭代系列的第一个整数。
- toExclusive参数是比迭代系列最后一个索引号大1的整数。也就是说,和使用表达式
index<ToExclusive计算一样。 - body是接受单个输入参数的委托,body的代码在每一次迭代中执行一次。
如下代码是使用Parallel.For结构的例子。它从0迭代到14(记住实际的参数巧超出了最
大迭代索引)并且打印出迭代索引和索引的平方。该应用程序满足各个迭代之间相互独立的条件。
还要注意,必须使用System.Threading.Tasks命名空间。
using System;
using System.Threading.Tasks;namespace ExampleParallerFor
{class Program{static void Main(){Parallel.For(0,15,i=>Console.WriteLine($"The square of{i} is{i*i}"));}}
}
另一个示例如下。程序以并行方式填充一个整数数组,把值设置为迭代索引号的平方。
class Program
{static void Main(){const int maxValues=50;int[] squares=new int[maxValues];Parallel.For(0,maxValues,i+>squares[i]=i*i);}
}
在本例中,尽管迭代可能并行执行,也能以任意顺序执行,但是最后结果始终是一个包含前
50个平方数的数组一一并且按顺序排列。
另外一个并行循环结构是Parallel.ForEach方法。该方法有相当多的重载,其中最简单的如下:
- TSource是集合中对象的类型;
- source是TSource对象的集合·
- body是要应用到集合中每一个元素上的Lambda表达式。
static ParallelLoopResult ForEach<TSource>(IEnumerable<TSource>source,Action<TSource>boday)
使用Paralle.ForEach方法的例子如下。在这里,TSource是string,source是string[]。
using System;
using System.Threading.Tasks;namespace ParallelForeach1
{class Program{static void Main(){string[] squares=new string[]{"We","hold","these","truths","to","be","self-evident","that","all","men","are","created","equal"};Parallel.ForEach(squares,s=>Console.WriteLine(string.Format($"\"{s}\" has{s.Length}letters")));}}
}
在我的双核处理器pc上运行这段代码时产生了如下输出,但是每一次运行都可能会有不一
样的顺序。
其他异步编程模式
如果我们要自己编写异步代码,最可能使用的就是本章前面介绍的async/await特性和
BackgroundWorker类,或者任务并行库。然而,你仍然有可能需要使用旧的模式来产生异步代码。
为了完整性,我们从现在开始介绍这些模式,直到本章结束。在学习了这些旧模式后,你将对
async/await特性是多么简单有更加深刻的认识。
第14章介绍了委托,我们了解到当委托对象被调用时,它调用其调用列表中包含的方法。
就像程序调用方法一样,这是同步完成的。
如果委托对象在调用列表中只有一个方法(之后会叫作引用方法),它就可以异步执行这个
方法。委托类有两个方法,叫作BeginInvcke和Endlnvoke,它们就是用来实现这个目的的。这
两个方法以如下方式使用。
- 当调用委托的BeginInvoke方法时,它开始在一个独立线程上执行引用方法,之后立即
返回到原始线程。原始线程可以继续,而引用方法会在线程池的线程中并行执行。 - 当程序希望获取已完成的异步方法的结果时,可以检查BeginInvoke返回的IAsyncResult
的IsCompleted属性,或调用委托的EndInvoke方法来等待委托完成。
图21-19演示了使用这一过程的三种标准模式。对于这三种模式来说,原始线程都发起了一
个异步方法调用,然后做一些其他处理。然而,这些模式的区别在于,原始线程如何知道发起的
线程已经完成。
- 在等待直到完成(wait-until-done)模式中,在发起了异步方法以及做了一些其他处理之
后,原始线程就中断并且等异步方法完成之后再继续。 - 在轮询(polling)模式中,原始线程定期检查发起的线程是否完成,如果没有则可以继续
做一些其他的事情。 - 在回调(callback)模式中,原始线程一直执行,无须等待或检查发起的线程是否完成。
在发起的线程中的引用方法完成之后,发起的线程就会调用回调方法,由回调方法在调
用EndInvoke之前处理异步方法的结果。