《在C#中竞争》一书。异步,并行和多线程编程。第二诠释 ed。”

图片您好,habrozhiteli!如果您担心竞争性和多线程编程,那么可以为您编写本书。 Stephen Cleary有85种使用.NET和C#8.0进行并行处理和异步编程的方法。竞争已经成为开发高度可扩展应用程序的公认方法,但是并发编程仍然是艰巨的任务。有关代码的详细示例和注释将帮助您了解现代工具如何提高抽象级别并简化竞争性编程。您将学习如何使用async并等待异步操作,通过使用异步线程来扩展代码的功能,探索使用TPL Dataflow库进行并行编程的潜力,使用TPL数据流库创建数据流管道,使用基于LINQ的系统。响应功能,使用线程安全且不可变的集合,对竞争代码进行单元测试,控制线程池,实施正确的协作取消,分析脚本以结合竞争方法,使用异步兼容的面向对象编程的所有功能,识别和创建使用旧风格的异步编程的代码的适配器。分析脚本以结合竞争方法,使用异步兼容的面向对象编程的所有功能,识别并创建使用旧样式的异步编程的代码的适配器。分析脚本以结合竞争方法,使用异步兼容的面向对象编程的所有功能,识别并创建使用旧样式的异步编程的代码的适配器。

并行编程基础


4.1。并行数据处理


任务


有数据收集。您必须对每个数据项执行相同的操作。此操作在计算上受到限制,可能需要一些时间。

决断


Parallel类型包含专门为此任务设计的ForEach方法。以下示例获取矩阵的集合并旋转这些矩阵:

void RotateMatrices(IEnumerable<Matrix> matrices, float degrees)
{
   Parallel.ForEach(matrices, matrix => matrix.Rotate(degrees));
}

在某些情况下,有必要过早终止循环(例如,如果检测到无效值)。以下示例颠倒了每个矩阵,但是如果发现无效的矩阵,则循环将被中断:

void InvertMatrices(IEnumerable<Matrix> matrices)
{
   Parallel.ForEach(matrices, (matrix, state) =>
   {
      if (!matrix.IsInvertible)
        state.Stop();
      else
        matrix.Invert();
   });
}

此代码使用ParallelLoopState.Stop停止循环并阻止对循环体的任何进一步调用。请记住,循环是并行的,因此,可能已经对循环主体进行了其他调用,包括对当前循环之后的元素的调用。在给定的代码示例中,如果第三个矩阵是不可逆的,则循环被中断并且不会处理新的矩阵,但事实证明其他矩阵已经在处理中(例如,第四个和第五个)。

当您需要取消并行循环时,会发生一种更常见的情况。这与停止循环不同;循环从内部停止,从外部取消。例如,取消按钮可以取消CancellationTokenSource,取消并行循环,如以下示例所示:

void RotateMatrices(IEnumerable<Matrix> matrices, float degrees,
      CancellationToken token)
{
   Parallel.ForEach(matrices,
         new ParallelOptions { CancellationToken = token },
         matrix => matrix.Rotate(degrees));
}

应该记住,每个并行任务都可以在不同的线程中执行,因此,任何联合状态都必须得到保护。下面的示例反转每个矩阵并计算无法反转的矩阵数:

// :     .
//      
//    .
int InvertMatrices(IEnumerable<Matrix> matrices)
{
  object mutex = new object();
  int nonInvertibleCount = 0;
  Parallel.ForEach(matrices, matrix =>
  {
     if (matrix.IsInvertible)
    {
       matrix.Invert();
    }
    else
    {
       lock (mutex)
      {
         ++nonInvertibleCount;
      }
    }
  });
  return nonInvertibleCount;
}

说明


Parallel.ForEach方法为一系列值提供并行处理。类似的并行LINQ(PLINQ)解决方案在类似LINQ的语法中提供了几乎相同的功能。Parallel和PLINQ之间的区别之一是PLINQ假定它可以使用计算机上的所有内核,而Parallel可以动态响应不断变化的处理器条件。

Parallel.ForEach实现并行的foreach循环。如果您需要执行并行for循环,则Parallel类还支持Parallel.For方法。当处理接收单个索引的多个数据数组时,Parallel.For方法特别有用。

附加信息


4.2节讨论了一系列值的并行汇总,包括求和和平均值计算。

4.5节介绍了PLINQ的基础知识。

第10章讨论取消。

4.2。并行聚合


任务


需要在并行操作结束时汇总结果(汇总示例是值的总和或平均值的计算)。

决断


为了支持聚合,Parallel类使用局部值的概念-局部变量存在于并行循环中。这意味着循环主体可以直接直接访问该值,而无需同步。当循环准备好汇总其所有本地结果时,它将使用localFinally委托进行此操作。应该注意的是,localFinally委托不需要同步对变量的访问来存储结果。并行求和的示例:

// :     .
//      
//    .
int ParallelSum(IEnumerable<int> values)
{
  object mutex = new object();
  int result = 0;
  Parallel.ForEach(source: values,
        localInit: () => 0,
        body: (item, state, localValue) => localValue + item,
        localFinally: localValue =>
       {
          lock (mutex)
             result += localValue;
       });
  return result;
}

并行LINQ提供了比Parallel类更全面的​​聚合支持:

int ParallelSum(IEnumerable<int> values)
{
   return values.AsParallel().Sum();
}

好的,这很便宜,因为PLINQ对许多常见的运算符(例如Sum)都有内置的支持。PLINQ还通过Aggregate运算符提供了通用的聚合支持:

int ParallelSum(IEnumerable<int> values)
{
  return values.AsParallel().Aggregate(
        seed: 0,
        func: (sum, item) => sum + item
  );
}

说明


如果已经在使用Parallel类,则应使用其聚合支持。在其他情况下,PLINQ支持通常更具表现力,并且代码更短。

附加信息


4.5节概述了PLINQ的基础。

4.3。平行通话


任务


有一组应并行调用的方法。这些方法(大多数)彼此独立。

决断


Parallel类包含一个为此类情况设计的简单Invoke方法。在下面的示例中,将数组分为两部分,并分别处理这两个部分:

void ProcessArray(double[] array)
{
   Parallel.Invoke(
         () => ProcessPartialArray(array, 0, array.Length / 2),
         () => ProcessPartialArray(array, array.Length / 2, array.Length)
   );
}

void ProcessPartialArray(double[] array, int begin, int end)
{
   // ,   ...
}

如果在执行之前调用的数目未知,则还可以将委托数组传递给Parallel.Invoke方法:

void DoAction20Times(Action action)
{
   Action[] actions = Enumerable.Repeat(action, 20).ToArray();
   Parallel.Invoke(actions);
}

Parallel.Invoke支持取消,与Parallel类的其他方法一样:

void DoAction20Times(Action action, CancellationToken token)
{
   Action[] actions = Enumerable.Repeat(action, 20).ToArray();
   Parallel.Invoke(new ParallelOptions { CancellationToken = token },
        actions);
}

说明


Parallel.Invoke方法是简单并行调用的理想解决方案。我注意到,它不太适合需要为每个输入数据元素激活一个动作的情况(最好使用Parallel.ForEach进行此操作),或者每个动作产生一些输出的情况(应该使用Parallel LINQ代替)。

附加信息


4.1节讨论了Parallel.ForEach方法,该方法为每个数据项执行一个操作。

4.5节涉及并行LINQ。

关于作者


经验丰富的开发人员Stephen Cleary从ARM转到了Azure。他为开源Boost C ++库做出了贡献,并发布了一些专有的库和实用程序。

»关于这本书的更多信息上可以找到出版商的网站
» 目录
» 摘录

的优惠券Khabrozhiteley 25%的折扣- 克利里

在缴付书的纸质版本,电子书是通过电子邮件发送。

All Articles