c# 高级编程 21章460页 【任务和并行编程】【Parallel类】

227 阅读2分钟

Parallel 类

  • 位于System.Threading.Task命名空间
  • 提供了数据和任务的并行性
  • 对线程的一个很好的抽象
  • 定义了静态方法:
    • For(): 在每个迭代中调用相同的代码
    • ForEach(): 在每个迭代中调用相同的代码
    • Invoke(): 允许同时调用不同的代码

Parallel.For()

  • 并行运行迭代
  • 迭代的顺序没有定义

示例

  • 查看是哪个Task:Task.CurrentId
  • 查看是哪个线程:Thread.CurrentThread.ManagedThreadId
public static void Log(string prefix) =>
            Console.WriteLine($"{prefix} task: {Task.CurrentId}, thread: {Thread.CurrentThread.ManagedThreadId}");

  • 前两个参数:0, 10 定义了循环的开头和结束。即从 0 迭代到 9
  • 后一个参数:Action<int>, i为当前的迭代次数
  • 返回值:ParallelLoopResult 是一个结构,提供了循环是否结束的信息
        public static void ParallelFor()
        {
            ParallelLoopResult result =
              Parallel.For(0, 10, i =>
              {
                  Log($"S {i}");
                  //阻塞当前线程,直到延迟结束
                  Task.Delay(10).Wait();
                  //end-log 使用 与 start-log 相同的线程和任务
                  Log($"E {i}");
              });
            Console.WriteLine($"Is completed: {result.IsCompleted}");
        }

输出:

  • 任务不一定映射到一个线程上
  • 线程也可以被不同的任务重用
>dotnet ParallelSamples.dll -p
S 6 task: 3, thread: 10
S 1 task: 2, thread: 3
S 3 task: 8, thread: 5
S 4 task: 7, thread: 6
S 5 task: 1, thread: 7
S 0 task: 5, thread: 1
S 7 task: 6, thread: 9
S 2 task: 4, thread: 4
S 8 task: 9, thread: 8
E 4 task: 7, thread: 6
E 1 task: 2, thread: 3
E 0 task: 5, thread: 1
E 2 task: 4, thread: 4
E 8 task: 9, thread: 8
E 7 task: 6, thread: 9
E 3 task: 8, thread: 5
S 9 task: 10, thread: 6
E 5 task: 1, thread: 7
E 6 task: 3, thread: 10
E 9 task: 10, thread: 6
Is completed: True

注释掉一行,则会使用更少的线程和任务

        public static void ParallelFor()
        {
            ParallelLoopResult result =
              Parallel.For(0, 10, i =>
              {
                  Log($"S {i}");
                  //注释掉
                  //Task.Delay(10).Wait();
                  Log($"E {i}");
              });
            Console.WriteLine($"Is completed: {result.IsCompleted}");
        }

输出:

>dotnet ParallelSamples.dll -p
S 0 task: 2, thread: 1
S 8 task: 9, thread: 10
S 7 task: 8, thread: 8
S 3 task: 4, thread: 5
S 1 task: 1, thread: 3
S 5 task: 6, thread: 6
S 6 task: 7, thread: 9
S 2 task: 3, thread: 4
S 4 task: 5, thread: 7
E 0 task: 2, thread: 1
E 3 task: 4, thread: 5
E 1 task: 1, thread: 3
E 2 task: 3, thread: 4
E 5 task: 6, thread: 6
E 4 task: 5, thread: 7
E 6 task: 7, thread: 9
E 7 task: 8, thread: 8
S 9 task: 2, thread: 1
E 8 task: 9, thread: 10
E 9 task: 2, thread: 1
Is completed: True

  • 只等待它创建的任务,而不等待其他后台活动。因此这里result很快就complete了
        public static void ParallelForWithAsync()
        {
            ParallelLoopResult result =
              Parallel.For(0, 10, async i =>
              {
                  Log($"S {i}");
                  // 不阻塞当前线程
                  await Task.Delay(10);
                  // 这时,task已经不存在了,只有线程了
                  Log($"E {i}");
              });
            
            Console.WriteLine($"Is completed: {result.IsCompleted}");
        }

输出:

>dotnet ParallelSamples.dll -pfa
S 1 task: 4, thread: 3
S 3 task: 8, thread: 5
S 4 task: 9, thread: 6
S 5 task: 6, thread: 8
S 0 task: 5, thread: 1
S 8 task: 2, thread: 10
S 6 task: 3, thread: 9
S 2 task: 7, thread: 4
S 7 task: 1, thread: 7
S 9 task: 5, thread: 1
Is completed: True
E 6 task: , thread: 6
E 4 task: , thread: 5
E 3 task: , thread: 10
E 7 task: , thread: 4
E 2 task: , thread: 8
E 0 task: , thread: 7
E 9 task: , thread: 3
E 8 task: , thread: 9
E 5 task: , thread: 6
E 1 task: , thread: 5

提前中断 Parallel.For()

  • For()的一个重载版本接受第三个参数Action<int, ParallelLoopState>
  • 可以调用ParallelLoopStateBreak()Stop()
        public static void StopParallelForEarly()
        {
            ParallelLoopResult result =
              Parallel.For(10, 40, (int i, ParallelLoopState pls) =>
              {
                  Log($"S {i}");
                  if (i > 12)
                  {
                      pls.Break();
                      Log($"break now {i}");
                  }
                  Task.Delay(10).Wait();
                  Log($"E {i}");
              });

            Console.WriteLine($"Is completed: {result.IsCompleted}");
            Console.WriteLine($"lowest break iteration: {result.LowestBreakIteration}");

        }

输出:

  • 中断前开始的所有任务都可以继续运行,直到结束
    • 例如: 22, 19,25, 16, 34, 28, 13, 31,它们开始于中断之前,因此,都会无视整个中断,一直执行到E, 甚至还执行了break
  • 一个迭代中断时,不符合中断条件的其他迭代仍可以运行,直到结束
    • 例如:11, 12
>dotnet ParallelSamples.dll -spfe
S 22 task: 8, thread: 6
S 10 task: 3, thread: 1
S 19 task: 2, thread: 5
break now 19 task: 2, thread: 5
S 25 task: 5, thread: 7
break now 25 task: 5, thread: 7
S 16 task: 1, thread: 4
S 34 task: 9, thread: 10
S 28 task: 4, thread: 9
break now 28 task: 4, thread: 9
S 13 task: 7, thread: 3
S 31 task: 6, thread: 8
break now 31 task: 6, thread: 8
break now 34 task: 9, thread: 10
break now 22 task: 8, thread: 6
break now 13 task: 7, thread: 3
break now 16 task: 1, thread: 4
E 10 task: 3, thread: 1
S 12 task: 3, thread: 1
E 19 task: 2, thread: 5
E 16 task: 1, thread: 4
E 13 task: 7, thread: 3
E 22 task: 8, thread: 6
E 28 task: 4, thread: 9
E 31 task: 6, thread: 8
E 25 task: 5, thread: 7
S 11 task: 10, thread: 12
E 34 task: 9, thread: 10
E 11 task: 10, thread: 12
E 12 task: 3, thread: 1
Is completed: False
lowest break iteration: 13

Parallel.For 在各个线程上 的初始化

ParallelFor()使用几个线程来执行循环。如果需要对每个线程进行初始化,就可以使用其泛型版本Parallel.For<TLocal>()方法 其泛型版本,还接受3个委托参数:

  • 第一个委托参数:Func<TLocal>, 只对每个线程调用一次
  • 第二个委托参数:Func<int, ParallelLoopState, TLocal, TLocal>, 分别是:
    • int: 循环迭代
    • ParallelLoopState: 允许停止循环
    • TLocal: 接受从 “init thread” 所在方法返回的值
    • TLocal: 循环体方法返回值
  • 第三个委托参数: Action<TLocal>, 是线程退出方法,对每个线程执行一次
        public static void ParallelForWithInit()
        {
            Parallel.For<string>(0, 100, () =>
            {
                // invoked once for each thread
                Log("init thread");
                return $"t{Thread.CurrentThread.ManagedThreadId}";
            },
            (i, pls, str1) =>
            {
                // invoked for each member
                Log($"body i: {i} str1: {str1}");
                Task.Delay(10).Wait();
                return $"i {i}";
            },
            (str1) =>
            {
                // final action on each thread
                Log($"finally {str1}");
            });
        }

输出:

>dotnet ParallelSamples.dll -pfi
init thread task: 8, thread: 9
init thread task: 6, thread: 7
init thread task: 1, thread: 1
init thread task: 9, thread: 10
init thread task: 4, thread: 5
init thread task: 3, thread: 4
init thread task: 7, thread: 8
init thread task: 2, thread: 3
init thread task: 5, thread: 6
body i: 48 str1: t6 task: 5, thread: 6
body i: 60 str1: t7 task: 6, thread: 7
body i: 84 str1: t9 task: 8, thread: 9
body i: 0 str1: t1 task: 1, thread: 1
body i: 36 str1: t5 task: 4, thread: 5
body i: 24 str1: t4 task: 3, thread: 4
body i: 72 str1: t8 task: 7, thread: 8
body i: 12 str1: t3 task: 2, thread: 3
body i: 96 str1: t10 task: 9, thread: 10
body i: 1 str1: i 0 task: 1, thread: 1
init thread task: 10, thread: 12
finally i 48 task: 5, thread: 6
body i: 3 str1: t12 task: 10, thread: 12
finally i 12 task: 2, thread: 3
finally i 96 task: 9, thread: 10
finally i 72 task: 7, thread: 8
init thread task: 14, thread: 8
body i: 73 str1: t8 task: 14, thread: 8
finally i 60 task: 6, thread: 7
finally i 24 task: 3, thread: 4
init thread task: 11, thread: 6
body i: 49 str1: t6 task: 11, thread: 6
init thread task: 13, thread: 10
body i: 97 str1: t10 task: 13, thread: 10
finally i 84 task: 8, thread: 9
init thread task: 17, thread: 9
body i: 85 str1: t9 task: 17, thread: 9
init thread task: 12, thread: 3
finally i 36 task: 4, thread: 5
init thread task: 18, thread: 5
body i: 37 str1: t5 task: 18, thread: 5
body i: 13 str1: t3 task: 12, thread: 3
init thread task: 15, thread: 7
body i: 61 str1: t7 task: 15, thread: 7
init thread task: 16, thread: 4
body i: 25 str1: t4 task: 16, thread: 4
body i: 74 str1: i 73 task: 14, thread: 8
body i: 2 str1: i 1 task: 1, thread: 1
body i: 50 str1: i 49 task: 11, thread: 6
body i: 62 str1: i 61 task: 15, thread: 7
body i: 26 str1: i 25 task: 16, thread: 4
finally i 3 task: 10, thread: 12
body i: 86 str1: i 85 task: 17, thread: 9
body i: 14 str1: i 13 task: 12, thread: 3
body i: 98 str1: i 97 task: 13, thread: 10
body i: 38 str1: i 37 task: 18, thread: 5
init thread task: 19, thread: 13
init thread task: 20, thread: 12
body i: 4 str1: t12 task: 20, thread: 12
body i: 15 str1: t13 task: 19, thread: 13
finally i 15 task: 19, thread: 13
body i: 6 str1: i 2 task: 1, thread: 1
finally i 50 task: 11, thread: 6
init thread task: 23, thread: 6
body i: 51 str1: t6 task: 23, thread: 6
finally i 14 task: 12, thread: 3
init thread task: 24, thread: 3
finally i 98 task: 13, thread: 10
init thread task: 25, thread: 10
body i: 99 str1: t10 task: 25, thread: 10
finally i 62 task: 15, thread: 7
init thread task: 22, thread: 14
body i: 27 str1: t14 task: 22, thread: 14
init thread task: 21, thread: 13
finally i 38 task: 18, thread: 5
finally i 86 task: 17, thread: 9
body i: 18 str1: t3 task: 24, thread: 3
body i: 5 str1: i 4 task: 20, thread: 12
finally i 74 task: 14, thread: 8
init thread task: 26, thread: 7
body i: 63 str1: t7 task: 26, thread: 7
body i: 16 str1: t13 task: 21, thread: 13
init thread task: 27, thread: 5
body i: 39 str1: t5 task: 27, thread: 5
init thread task: 29, thread: 8
body i: 75 str1: t8 task: 29, thread: 8
init thread task: 28, thread: 9
body i: 87 str1: t9 task: 28, thread: 9
finally i 26 task: 16, thread: 4
init thread task: 30, thread: 4
body i: 28 str1: t4 task: 30, thread: 4
body i: 52 str1: i 51 task: 23, thread: 6
body i: 88 str1: i 87 task: 28, thread: 9
finally i 27 task: 22, thread: 14
init thread task: 32, thread: 14
body i: 32 str1: t14 task: 32, thread: 14
body i: 76 str1: i 75 task: 29, thread: 8
finally i 99 task: 25, thread: 10
init thread task: 33, thread: 10
body i: 17 str1: i 16 task: 21, thread: 13
finally i 5 task: 20, thread: 12
body i: 40 str1: i 39 task: 27, thread: 5
body i: 64 str1: i 63 task: 26, thread: 7
init thread task: 31, thread: 15
body i: 43 str1: t15 task: 31, thread: 15
body i: 7 str1: i 6 task: 1, thread: 1
body i: 19 str1: i 18 task: 24, thread: 3
body i: 10 str1: t10 task: 33, thread: 10
init thread task: 34, thread: 12
body i: 22 str1: t12 task: 34, thread: 12
body i: 29 str1: i 28 task: 30, thread: 4
body i: 53 str1: i 52 task: 23, thread: 6
body i: 33 str1: i 32 task: 32, thread: 14
body i: 11 str1: i 10 task: 33, thread: 10
body i: 23 str1: i 22 task: 34, thread: 12
body i: 30 str1: i 29 task: 30, thread: 4
body i: 8 str1: i 7 task: 1, thread: 1
body i: 65 str1: i 64 task: 26, thread: 7
body i: 89 str1: i 88 task: 28, thread: 9
body i: 41 str1: i 40 task: 27, thread: 5
finally i 17 task: 21, thread: 13
body i: 77 str1: i 76 task: 29, thread: 8
finally i 43 task: 31, thread: 15
init thread task: 37, thread: 15
body i: 44 str1: t15 task: 37, thread: 15
init thread task: 36, thread: 13
body i: 20 str1: i 19 task: 24, thread: 3
init thread task: 35, thread: 16
body i: 55 str1: t16 task: 35, thread: 16
body i: 34 str1: t13 task: 36, thread: 13
body i: 54 str1: i 53 task: 23, thread: 6
body i: 42 str1: i 41 task: 27, thread: 5
body i: 35 str1: i 34 task: 36, thread: 13
finally i 33 task: 32, thread: 14
body i: 21 str1: i 20 task: 24, thread: 3
body i: 9 str1: i 8 task: 1, thread: 1
body i: 90 str1: i 89 task: 28, thread: 9
finally i 11 task: 33, thread: 10
body i: 66 str1: i 65 task: 26, thread: 7
body i: 78 str1: i 77 task: 29, thread: 8
finally i 23 task: 34, thread: 12
init thread task: 41, thread: 12
body i: 68 str1: t12 task: 41, thread: 12
finally i 55 task: 35, thread: 16
init thread task: 42, thread: 16
body i: 79 str1: t16 task: 42, thread: 16
init thread task: 40, thread: 10
body i: 56 str1: t10 task: 40, thread: 10
body i: 31 str1: i 30 task: 30, thread: 4
init thread task: 38, thread: 17
body i: 67 str1: t17 task: 38, thread: 17
body i: 45 str1: i 44 task: 37, thread: 15
init thread task: 39, thread: 14
body i: 46 str1: t14 task: 39, thread: 14
finally i 54 task: 23, thread: 6
finally i 90 task: 28, thread: 9
body i: 57 str1: i 56 task: 40, thread: 10
body i: 80 str1: i 79 task: 42, thread: 16
finally i 45 task: 37, thread: 15
finally i 67 task: 38, thread: 17
body i: 81 str1: i 9 task: 1, thread: 1
body i: 47 str1: i 46 task: 39, thread: 14
finally i 66 task: 26, thread: 7
finally i 42 task: 27, thread: 5
body i: 69 str1: i 68 task: 41, thread: 12
init thread task: 43, thread: 6
body i: 91 str1: t6 task: 43, thread: 6
finally i 35 task: 36, thread: 13
finally i 31 task: 30, thread: 4
finally i 21 task: 24, thread: 3
finally i 78 task: 29, thread: 8
finally i 47 task: 39, thread: 14
body i: 58 str1: i 57 task: 40, thread: 10
body i: 92 str1: i 91 task: 43, thread: 6
body i: 70 str1: i 69 task: 41, thread: 12
body i: 82 str1: i 81 task: 1, thread: 1
finally i 80 task: 42, thread: 16
body i: 93 str1: i 92 task: 43, thread: 6
body i: 59 str1: i 58 task: 40, thread: 10
body i: 71 str1: i 70 task: 41, thread: 12
body i: 83 str1: i 82 task: 1, thread: 1
body i: 94 str1: i 93 task: 43, thread: 6
finally i 59 task: 40, thread: 10
finally i 71 task: 41, thread: 12
finally i 83 task: 1, thread: 1
body i: 95 str1: i 94 task: 43, thread: 6
finally i 95 task: 43, thread: 6

Parallel.ForEach()

  • 以异步方式遍历实现了IEnumerable的集合
  • 没有确定的遍历顺序
  • 重载版本:
    • 可以用ParallelLoopState中断循环
    • 可以获得迭代次数
        public static void ParallelForEach()
        {
            string[] data = {"zero", "one", "two", "three", "four", "five",
                "six", "seven", "eight", "nine", "ten", "eleven", "twelve"};
            ParallelLoopResult result =
              Parallel.ForEach<string>(data, s =>
              {
                  Console.WriteLine(s);
              });
        }

输出:

>dotnet ParallelSamples.dll -pfe
one
two
three
four
five
zero
seven
six
eight
nine
ten
eleven
twelve
        // pls: ParallelLoopState
        // l: 迭代次数
        Parallel.ForEach<string>(data, (s, pls, l) =>
        {
            Console.WriteLine($"{s} {l}");
        });

Parallel.Invoke()

多个任务并行运行,可以用 Parallel.Invoke()

  • 参数:一个Action委托的数组
        public static void ParallelInvoke()
        {
            Parallel.Invoke(Foo, Bar);
        }

        private static void Foo() =>
            Console.WriteLine("foo");

        private static void Bar() =>
            Console.WriteLine("bar");

比较:Parallel 类和Task 类

Parallel类用起来很方便

  • 既可以用于任务
  • 又可以用于数据并行性

如果:

  • 需要更细致的控制
  • 并且不想等到Parallel类结束后再开始动作

就可以使用Task

Task类和Parallel类结合使用,也是可以的