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>
- 可以调用
ParallelLoopState
的Break()
或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
类结合使用,也是可以的