C# 异步编程(并行循环)

发布于:2025-08-11 ⋅ 阅读:(14) ⋅ 点赞:(0)

并行循环

本节将简要介绍任务并行库(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之前处理异步方法的结果。

异步方法调用的标准模式