记录《Effective C#》学习过程。

通过Parallel LINQ (PLINQ) 实现并行算法

通过使用PLINQ提供的方法,可以简单的写出发挥多核CPU优势的代码,也会有线程开销。

对数据源调用AsParallel()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class Test
{
public static void MainMethod()
{
var students = new List<Student>() {
new Student(){Age=10 },new Student(){ Age=9},new Student(){ Age=9},new Student(){ Age=9}
new Student(){ Age=12}};

//linq to objects
IEnumerable<Student> filterStudents = students.Where(m => m.Age < 10).
Select(n => SetStudent(n));

//对数据源做AsParallel处理 不涉及数据共享,元素顺序没有特定要求。
//public class ParallelQuery<TSource> : ParallelQuery, IEnumerable<TSource>, IEnumerable
ParallelQuery<Student> filterStudents1 = students.AsParallel().Where(m => m.Age < 10).
Select(n => SetStudent(n));

//linq to sql
var filterStudents2 = from m in students
where m.Age < 10
select SetStudent(m);

var filterStudents3 = from m in students.AsParallel()
where m.Age < 10
select SetStudent(m);

}

private static Student SetStudent(Student student)
{
student.TeacherName = "李楚刀";
return student;
}
}

internal class Student
{
public int Age { get; set; }

public string TeacherName { get; set; }
}

数据划分,当执行并行查询的时候,第一个步骤是划分数据,划分数据方式包括范围划分、区块划分、带状划分、哈希划分。PLINQ自动的使用适当的算法对数据进行分区,并行并行执行查询的各个部分,然后合并结果。数据并行模式和PLINQ

并行执行任务的算法:pipelining、 stop and go、inverted enumeration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

//采用 stop & go 算法
var filterStudents3 = (from m in students.AsParallel()
where m.Age < 10
select SetStudent(m)).ToList();
//采用 stop & go 算法
var filterStudents4 = (from m in students.AsParallel()
where m.Age < 10
select SetStudent(m)).ToArray();

//采用 inverted enumeration 算法
var filterStudents5 = from m in students.AsParallel()
where m.Age < 10
select SetStudent(m);
//一边计算结果 一边对已经计算出来的元素并行地操作
filterStudents5.ForAll(item => Console.WriteLine(item));

LINQ to Objects 是以懒性的方式对查询操作进行求值,只有真正用到查询结果某个值的时候,系统才会去生成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
var answer = from n in Enumerable.Range(0, 300)
where n.SomeTest()
select n.SomeProjection();

var iter = answer.GetEnumerator();
Console.WriteLine("start iterating");
while (iter.MoveNext())
{
Console.WriteLine("called MoveNext");
Console.WriteLine(iter.Current);
}
}
}

public static class Test
{
public static bool SomeTest(this int inputValue)
{
Console.WriteLine($"testing element:{inputValue}");
return inputValue % 10 == 0;
}

public static string SomeProjection(this int input)
{
Console.WriteLine($"projecting an element:{input}");
return $"Delivered {input} at {DateTime.Now:T}";
}
}
}

截取部分结果说明:
start iterating
testing element:0
projecting an element:0
called MoveNext
Delivered 0 at 15:45:36
testing element:1
testing element:2
testing element:3
testing element:4
testing element:5
testing element:6
testing element:7
testing element:8
testing element:9
testing element:10
projecting an element:10
called MoveNext
Delivered 10 at 15:45:36
testing element:11 又去查询元素
testing element:12
testing element:13
testing element:14
testing element:15
testing element:16
testing element:17
testing element:18
testing element:19
testing element:20
projecting an element:20
called MoveNext
Delivered 20 at 15:45:36
testing element:21
testing element:22
testing element:23
testing element:24
testing element:25
testing element:26
testing element:27
testing element:28
testing element:29
testing element:30
projecting an element:30
called MoveNext
Delivered 30 at 15:45:36
testing element:31
testing element:32
testing element:33
testing element:34
testing element:35
testing element:36
testing element:37
testing element:38
testing element:39
testing element:40
projecting an element:40
called MoveNext
Delivered 40 at 15:45:36
testing element:41
testing element:42
testing element:43
testing element:44
testing element:45
testing element:46
testing element:47
testing element:48
testing element:49
testing element:50
projecting an element:50
called MoveNext
Delivered 50 at 15:45:36

当程序下次调用MoveNext()方法时,系统只会执行到能够产生下一项执行结果的地方。

上面的LINQ to Objects 改用为 PLINQ执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158

namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
var answer = from n in ParallelEnumerable.Range(0, 300)
where n.SomeTest()
select n.SomeProjection();

var iter = answer.GetEnumerator();
Console.WriteLine("start iterating");
while (iter.MoveNext())
{
Console.WriteLine("called MoveNext");
Console.WriteLine(iter.Current);
}
}
}

public static class Test
{
public static bool SomeTest(this int inputValue)
{
Console.WriteLine($"testing element:{inputValue};当前线程:{Thread.CurrentThread.ManagedThreadId}");
return inputValue % 10 == 0;
}

public static string SomeProjection(this int input)
{
Console.WriteLine($"projecting an element:{input}");
return $"Delivered {input} at {DateTime.Now:T}";
}
}
}

截取部分结果说明:
start iterating
testing element:0;当前线程:4
testing element:150;当前线程:5
testing element:75;当前线程:3
testing element:225;当前线程:6
testing element:226;当前线程:6
testing element:227;当前线程:6
testing element:228;当前线程:6
testing element:229;当前线程:6
testing element:230;当前线程:6
projecting an element:230
projecting an element:0
projecting an element:150
testing element:76;当前线程:3
testing element:77;当前线程:3
testing element:78;当前线程:3
testing element:79;当前线程:3
testing element:80;当前线程:3
projecting an element:80
testing element:151;当前线程:5
testing element:152;当前线程:5
testing element:153;当前线程:5
testing element:154;当前线程:5
testing element:155;当前线程:5
testing element:156;当前线程:5
testing element:157;当前线程:5
testing element:158;当前线程:5
testing element:159;当前线程:5
testing element:160;当前线程:5
projecting an element:160
testing element:161;当前线程:5
testing element:162;当前线程:5
testing element:231;当前线程:6
testing element:232;当前线程:6
testing element:233;当前线程:6
testing element:234;当前线程:6
testing element:81;当前线程:3
testing element:1;当前线程:4
testing element:163;当前线程:5
testing element:235;当前线程:6
......
called MoveNext
Delivered 230 at 16:05:15
testing element:223;当前线程:5
testing element:131;当前线程:3
testing element:132;当前线程:3
testing element:224;当前线程:5
called MoveNext
Delivered 20 at 16:05:15
testing element:133;当前线程:3
testing element:134;当前线程:3
testing element:135;当前线程:3
testing element:136;当前线程:3
testing element:137;当前线程:3
testing element:138;当前线程:3
testing element:139;当前线程:3
testing element:140;当前线程:3
called MoveNext
projecting an element:140
testing element:141;当前线程:3
testing element:142;当前线程:3
testing element:143;当前线程:3
testing element:144;当前线程:3
testing element:145;当前线程:3
Delivered 150 at 16:05:15
called MoveNext
Delivered 240 at 16:05:15
called MoveNext
Delivered 30 at 16:05:15
called MoveNext
Delivered 160 at 16:05:15
called MoveNext 出现了交叉情况
testing element:146;当前线程:3
testing element:147;当前线程:3
testing element:148;当前线程:3
testing element:149;当前线程:3
Delivered 250 at 16:05:15
called MoveNext
Delivered 40 at 16:05:15
called MoveNext
Delivered 80 at 16:05:15
called MoveNext
Delivered 170 at 16:05:15
called MoveNext
Delivered 260 at 16:05:15
called MoveNext
Delivered 50 at 16:05:15
called MoveNext
Delivered 90 at 16:05:15
called MoveNext
Delivered 180 at 16:05:15
called MoveNext
Delivered 270 at 16:05:15
called MoveNext
Delivered 60 at 16:05:15
called MoveNext
Delivered 100 at 16:05:15
called MoveNext
Delivered 190 at 16:05:15
called MoveNext
Delivered 280 at 16:05:15
called MoveNext
Delivered 70 at 16:05:15
called MoveNext
Delivered 110 at 16:05:15
called MoveNext
Delivered 200 at 16:05:15
called MoveNext
Delivered 290 at 16:05:15
called MoveNext
Delivered 120 at 16:05:15
called MoveNext
Delivered 210 at 16:05:15
called MoveNext
Delivered 130 at 16:05:15
called MoveNext
Delivered 220 at 16:05:15
called MoveNext
Delivered 140 at 16:05:15
请按任意键继续. . .

程序刚一调用MoveNext()方法,PLINQ马上启动多个线程来计算查询结果。

并行算法的并行程度可能会因为执行的操作受到影响,例如查询里有OrderBy、ThenBy方法,各个任务需要进行协调,还有Skip、SkipWhile、Take、TakeWhile方法也会影响并行程度。

如果想让PLINQ在计算结果时保留源数据的的顺序或者无序,可以通过AsOrdered和AsUnOrdered方法

1
2
3
4
5
6
7
var answer = (from n in ParallelEnumerable.Range(0, 300).AsOrdered()
where n.SomeTest()
select n.SomeProjection()).Skip(10).Take(20);

var answer1 = (from n in ParallelEnumerable.Range(0, 300).AsUnordered()
where n.SomeTest()
select n.SomeProjection()).Skip(10).Take(20);

并行算法里如果有部分逻辑不能以并行方式执行,可以通过AsSequential()方法,将并行序列转换到IEnumerable。

即使使用了PLINQ,它也不一定会并行,只要当它认为会提供效率才会做出并行处理,如果想强制并行处理,可以使用WithExecutionMode(ParallelExecutionMode.ForceParallelism)

PLINQ的并行线程数默认是根据当前计算机的处理器核心数,可以使用WithDegreeOfParallelism

方法设置并行度。

一般来说,PLINQ会把已经算出来的某些结果先放进缓冲区中,稍后公布给消费线程。可以使用

WithMergeOptions方式建议PLINQ使用其他缓冲方式。是建议,可能被PLINQ忽略。

即使有了PLINQ,但是并行算法依然不好设计,设计不好也不会提高效率。应该做的是寻找程序中的循环或者其他能够用并行方式来处理的任务,试着改用并行版本,再衡量效果。

处理并行算法的异常

并行算法抛出的异常是由AggregateException包裹着。如果并行操作不止一项,AggregateException会嵌套。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Program
{
static void Main(string[] args)
{
try
{
var answer = (from n in ParallelEnumerable.Range(0, 300)
where n.SomeTest()
select n.SomeProjection()).Skip(10).Take(20);

var iter = answer.GetEnumerator();
Console.WriteLine("start iterating");
while (iter.MoveNext())
{
Console.WriteLine("called MoveNext");
Console.WriteLine(iter.Current);
}
}
catch(AggregateException ex)
{
ReportAggregateException(ex); //如果有多项并行操作,该递归,否则不递归也可以,递归的可以通用
}

}

private static void ReportAggregateException(AggregateException aggregateException)
{
foreach(var exception in aggregateException.InnerExceptions)
{
if(exception is AggregateException agEx)
{
ReportAggregateException(agEx);
Console.WriteLine("嵌套了");
}
else
{
Console.WriteLine(exception.Message);
}
}
}
}

如果希望有些异常想抛出给调用者,而有些异常当前方法处理掉就算了,可以用字典Dictionary<Type,Action<Exception>>,针对不同的异常,有不同的处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
static void Test()
{
try
{
var answer = (from n in ParallelEnumerable.Range(0, 300)
where n.SomeTest()
select n.SomeProjection()).Skip(10).Take(20);

var iter = answer.GetEnumerator();
Console.WriteLine("start iterating");
while (iter.MoveNext())
{
Console.WriteLine("called MoveNext");
Console.WriteLine(iter.Current);
}
}
catch (AggregateException ex)
{
var handlers = new Dictionary<Type, Action<Exception>>();
handlers.Add(typeof(WebException), error => Console.WriteLine(error.Message));

if (!HandleAggregateError(ex, handlers))
{
throw;
}
}
}

private static bool HandleAggregateError(AggregateException error, Dictionary<Type, Action<Exception>> errorHandles)
{
foreach (var exception in error.InnerExceptions)
{
if (exception is AggregateException agEx)
{
if (!HandleAggregateError(agEx, errorHandles))
{
return false;
}
else
{
continue;
}
}
else if (errorHandles.ContainsKey(exception.GetType()))
{
errorHandles[exception.GetType()](exception);
}
else
{
return false;
}
}
return true;
}

考虑详细一点的话,还可以在并行任务里面处理异常,至于哪些异常需要抛出去,哪些不需要,好好衡量。

线程池优于创建新线程

线程的数量等于目标计算机的CPU核心数,未必效率最高,还有其他资源争夺也会影响最佳线程数。

编写多线程代码时,如果可以并行,最好使用任务并行库,让这个底层程序库管理线程池,它可以根据当前可供使用的系统资源来适当的启动任务。

关于性能:任务库>单独创建线程池(线程可重复使用)>单独创建线程(线程不能重复使用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
 
//第一种 Task.Run
//var tasks = new List<Task>();
//tasks.Add(Task.Run(() => { }));
//tasks.Add(Task.Run(() => { }));
//tasks.Add(Task.Run(() => { }));
//await Task.WhenAll(tasks);

string answer = "";
int threads = 4;

using (AutoResetEvent e = new AutoResetEvent(false))
{
//第二种 线程池
//System.Threading.ThreadPool.QueueUserWorkItem((e0) => { }, "nn0");
//System.Threading.ThreadPool.QueueUserWorkItem((e1) => { }, "nn1");
//System.Threading.ThreadPool.QueueUserWorkItem((e2) => { answer=e2.ToString(); e.Set(); }, "nn2");
//e.WaitOne();

//创建新线程
System.Threading.Thread thread = new Thread(() => { });
thread.Start();
System.Threading.Thread thread1 = new Thread(() => { });
thread1.Start();
System.Threading.Thread thread2 = new Thread(() => { if (Interlocked.Decrement(ref threads) == 0) e.Set(); });
thread2.Start();
System.Threading.Thread thread3 = new Thread(() => { });
thread3.Start();

e.WaitOne();
}
BackgroundWorker

<<<<<<< HEAD

Microsoft BackgroundWorker

BackgroundWorker详细使用举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
static void Main(string[] args)
{
BackgroundWorker worker = new BackgroundWorker();
//运行
worker.DoWork += (object sender, DoWorkEventArgs e) =>
{
BackgroundWorker backgroundWorker = sender as BackgroundWorker;
int sum = 0;
for (int i = 0; i <= (int)e.Argument; i++)
{
if (i % 10 == 0)
{
backgroundWorker.ReportProgress(i / 10, "客官稍等片刻,正在加速运行...");
}
sum += i;
}
};
worker.WorkerReportsProgress = true; //进度
worker.ProgressChanged += (object sender, ProgressChangedEventArgs e) =>
{
Console.WriteLine($"当前进度:{e.ProgressPercentage}{ e.UserState.ToString()}");
};
worker.RunWorkerAsync(100); //开始运行
Console.ReadLine();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
BackgroundWorker worker = new BackgroundWorker();
//运行
worker.DoWork += (object sender, DoWorkEventArgs e) =>
{
BackgroundWorker backgroundWorker = sender as BackgroundWorker;
int sum = 0;
for (int i = 0; i <= (int)e.Argument; i++)
{
sum += i;
}
Thread.Sleep(3000);
if (worker.CancellationPending == true) //取消判断
{
return;
}
for (int i = 0; i <= (int)e.Argument; i++)
{
sum += i;
}
};

worker.WorkerSupportsCancellation = true; //支持取消
worker.RunWorkerAsync(100); //开始运行
worker.CancelAsync(); //取消
Console.ReadLine();
WPF和Winform的跨线程调用

同步的Invoke和异步的BeginInvoke、EndInvoke方法。

WPF通用跨线程静态类设计,使用Dispatcher判断线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public static class XAMLControlExtensions
{
public static void InvokeIfNeed(this System.Windows.Threading.DispatcherObject dispatcherObject,
Action doit,System.Windows.Threading.DispatcherPriority priority)
{
if(System.Threading.Thread.CurrentThread != dispatcherObject.Dispatcher.Thread)
{
dispatcherObject.Dispatcher.Invoke(priority, doit);//同步
}
else
{
doit();
}
}
public static void InvokeIfNeed<T>(this System.Windows.Threading.DispatcherObject dispatcherObject,
Action<T> action,T args,System.Windows.Threading.DispatcherPriority priority)
{
if(System.Threading.Thread.CurrentThread != dispatcherObject.Dispatcher.Thread)
{
dispatcherObject.Dispatcher.Invoke(priority, action, args);//同步,回到UI线程执行action
}
else
{
action(args);
}
}
}

Winform跨线程调用静态类设计、使用InvokeRequire判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public static class ControlExtensions
{
public static void InvokeIfNeed(this Control control,Action action)
{
//???
if(control.IsHandleCreated == false)
{
action();
}
else if(control.InvokeRequired){
control.Invoke(action);
}
else
{
action();
}
}

public static void InvokeIfNeed<T>(this Control control, Action<T> action,T args)
{
//???
if (control.IsHandleCreated == false)
{
action(args);
}
else if (control.InvokeRequired)
{
control.Invoke(action,args);
}
else
{
action(args);
}
}
public static void InvokeAsync(this Control control,Action action)
{
//异步
control.BeginInvoke(action);
}
public static void InvokeAsync<T>(this Control control, Action<T> action,T args)
{
control.BeginInvoke(action,args);
}
}

Winform通过InvokeRequired判断,如果控件已经创建,InvokeRequired判断会很迅速,如果控件还没完全创建好,会多花一点时间,但整个程序一个控件都还没创建成功,那InvokeRequired可能会返回错误值。

WPF通过Dispacher判断,已经对某些特殊情况做了优化,比Winform的要好。

Invoke方法,会像目标Control的消息队列投递消息,把delegate需要的内容放进去,放进去的参数是副本。当目标控件处理消息时,会处理整个队列的消息。Invoke方法会反复去查询结果。如果目标控件同时有BeginInvoke和Invoke消息,容易出问题。

WPF可以通过DispatcherPriority设置消息执行的优先级。

b3f2daae1cafe377c3f97d64c485e331954f3821

Microsoft BackgroundWorker

BackgroundWorker详细使用举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
static void Main(string[] args)
{
BackgroundWorker worker = new BackgroundWorker();
//运行
worker.DoWork += (object sender, DoWorkEventArgs e) =>
{
BackgroundWorker backgroundWorker = sender as BackgroundWorker;
int sum = 0;
for (int i = 0; i <= (int)e.Argument; i++)
{
if (i % 10 == 0)
{
backgroundWorker.ReportProgress(i / 10, "客官稍等片刻,正在加速运行...");
}
sum += i;
}
};
worker.WorkerReportsProgress = true; //进度
worker.ProgressChanged += (object sender, ProgressChangedEventArgs e) =>
{
Console.WriteLine($"当前进度:{e.ProgressPercentage}{ e.UserState.ToString()}");
};
worker.RunWorkerAsync(100); //开始运行
Console.ReadLine();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
BackgroundWorker worker = new BackgroundWorker();
//运行
worker.DoWork += (object sender, DoWorkEventArgs e) =>
{
BackgroundWorker backgroundWorker = sender as BackgroundWorker;
int sum = 0;
for (int i = 0; i <= (int)e.Argument; i++)
{
sum += i;
}
Thread.Sleep(3000);
if (worker.CancellationPending == true) //取消判断
{
return;
}
for (int i = 0; i <= (int)e.Argument; i++)
{
sum += i;
}
};

worker.WorkerSupportsCancellation = true; //支持取消
worker.RunWorkerAsync(100); //开始运行
worker.CancelAsync(); //取消
Console.ReadLine();
WPF和Winform的跨线程调用

同步的Invoke和异步的BeginInvoke、EndInvoke方法。

WPF通用跨线程静态类设计,使用Dispatcher判断线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public static class XAMLControlExtensions
{
public static void InvokeIfNeed(this System.Windows.Threading.DispatcherObject dispatcherObject,
Action doit,System.Windows.Threading.DispatcherPriority priority)
{
if(System.Threading.Thread.CurrentThread != dispatcherObject.Dispatcher.Thread)
{
dispatcherObject.Dispatcher.Invoke(priority, doit);//同步
}
else
{
doit();
}
}
public static void InvokeIfNeed<T>(this System.Windows.Threading.DispatcherObject dispatcherObject,
Action<T> action,T args,System.Windows.Threading.DispatcherPriority priority)
{
if(System.Threading.Thread.CurrentThread != dispatcherObject.Dispatcher.Thread)
{
dispatcherObject.Dispatcher.Invoke(priority, action, args);//同步,回到UI线程执行action
}
else
{
action(args);
}
}
}

Winform跨线程调用静态类设计、使用InvokeRequire判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public static class ControlExtensions
{
public static void InvokeIfNeed(this Control control,Action action)
{
//???
if(control.IsHandleCreated == false)
{
action();
}
else if(control.InvokeRequired){
control.Invoke(action);
}
else
{
action();
}
}

public static void InvokeIfNeed<T>(this Control control, Action<T> action,T args)
{
//???
if (control.IsHandleCreated == false)
{
action(args);
}
else if (control.InvokeRequired)
{
control.Invoke(action,args);
}
else
{
action(args);
}
}
public static void InvokeAsync(this Control control,Action action)
{
//异步
control.BeginInvoke(action);
}
public static void InvokeAsync<T>(this Control control, Action<T> action,T args)
{
control.BeginInvoke(action,args);
}
}

Winform通过InvokeRequired判断,如果控件已经创建,InvokeRequired判断会很迅速,如果控件还没完全创建好,会多花一点时间,但整个程序一个控件都还没创建成功,那InvokeRequired可能会返回错误值。

WPF通过Dispacher判断,已经对某些特殊情况做了优化,比Winform的要好。

Invoke方法,会像目标Control的消息队列投递消息,把delegate需要的内容放进去,放进去的参数是副本。当目标控件处理消息时,会处理整个队列的消息。Invoke方法会反复去查询结果。如果目标控件同时有BeginInvoke和Invoke消息,容易出问题。

WPF可以通过DispatcherPriority设置消息执行的优先级。

首先考虑Lock方法实现同步

多个线程共享同一份资源时,数据可能遭到破坏,一般可以考虑Lock方法同步,Lock方法是Monitor.Enter和Monitor.Exit的进一层封装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
object syncHandle = new object();

int total = 0;

public int TotalValue
{
get
{
lock (syncHandle) //只能锁引用类型
{
return total;
}
}
}

但是如果加锁的地方和解锁地方不在一块,那只能使用Monitor。

在一些大型系统中关键资源在多个线程上共享,可能会出现死锁,可以考虑改用Monitor.TryEnter方法,它可以在指定时间内尝试加锁,不会一直停留那里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
    public void IncrementTotal()
{
if (Monitor.TryEnter(syncHandle1, 1000))
{
try
{
total++;
}
finally
{
Monitor.Exit(syncHandle1);
}
}
}


//写成通用类,可以using直接使用
public sealed class LockHolder<T> : IDisposable where T : class
{
private T handle;
private bool holdsLock;

public LockHolder(T handle,int millisecondsTimeout)
{
this.handle = handle;
holdsLock = System.Threading.Monitor.TryEnter(handle, millisecondsTimeout);
}

public bool LockSuccessful
{
get
{
return holdsLock;
}
}

public void Dispose()
{
if (holdsLock)
{
System.Threading.Monitor.Exit(handle);
}
holdsLock = false;
}
}
}

//调用者
public void Test()
{
using (LockHolder<object> lockObject = new LockHolder<object>(syncHandle1,1000))
{
if (lockObject.LockSuccessful)
{
total++;
}
}
}

简单点的原子操作可以使用InterLock封装的方法

1
2
3
4
5
6
7
8
9
10
public void Test()
{
total = 20;

//简单的原子操作
System.Threading.Interlocked.Increment(ref total); //递增
System.Threading.Interlocked.Decrement(ref total); //递减
System.Threading.Interlocked.Exchange(ref total, 90); //替换
System.Threading.Interlocked.CompareExchange(ref total, 20, 80);//相等则替换
}

关于Monitor.Wait() ,暂时释放锁,阻塞当前线程,直到被唤醒 。Monitor.Pulse()唤醒等待的线程。

这两个方法主要用于争夺同一把锁的多个线程的锁的交互。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
namespace ConsoleApp1
{
public class LockMe
{
}

class WaitPulse1
{
private LockMe lM;

public WaitPulse1(LockMe l)
{
this.lM = l;
}

public void CriticalSection()
{
Monitor.Enter(this.lM);
//Enter the Critical Section
Console.WriteLine($"WaitPulse1: 线程{ Thread.CurrentThread.ManagedThreadId}");

for (int i = 1; i <= 5; i++)
{
Monitor.Wait(this.lM); //暂时释放锁、阻塞 直到被唤醒
Console.WriteLine($"WaitPulse1: 修改总数 {Program.total++} " +
$"线程 {Thread.CurrentThread.ManagedThreadId}");
Monitor.Pulse(this.lM); //唤醒
Console.WriteLine($"WaitPulse1: 干自己的事");
}
Console.WriteLine("WaitPulse1: 退出线程 "
+ Thread.CurrentThread.ManagedThreadId);

//释放锁
Monitor.Exit(this.lM);
}
}

class WaitPulse2
{
private LockMe lM;

public WaitPulse2(LockMe l)
{
this.lM = l;
}

public void CriticalSection()
{
Monitor.Enter(this.lM);
//Enter the Critical Section

for (int i = 1; i <= 5; i++)
{
Monitor.Pulse(this.lM); //唤醒
Console.WriteLine("WaitPulse2 干自己的事");
Monitor.Wait(this.lM);
Console.WriteLine("WaitPulse2: 修改总数 "
+ Program.total++
+ " 线程 "
+ Thread.CurrentThread.ManagedThreadId);
}
Console.WriteLine("WaitPulse2: 退出线程 "
+ Thread.CurrentThread.ManagedThreadId);

//释放锁
Monitor.Exit(this.lM);
}
}


class Program
{
public static int total = 0;

public static void Main(string[] args)
{
LockMe l = new LockMe();

WaitPulse1 e1 = new WaitPulse1(l);
WaitPulse2 e2 = new WaitPulse2(l);

Thread t1 = new Thread(new ThreadStart(e1.CriticalSection));
t1.Start();

Thread t2 = new Thread(new ThreadStart(e2.CriticalSection));
t2.Start();

//Wait till the user enters something
Console.ReadLine();
}
}
}


//结果
WaitPulse1: 线程3
WaitPulse2 干自己的事
WaitPulse1: 修改总数 0 线程 3
WaitPulse1: 干自己的事
WaitPulse2: 修改总数 1 线程 4
WaitPulse2 干自己的事
WaitPulse1: 修改总数 2 线程 3
WaitPulse1: 干自己的事
WaitPulse2: 修改总数 3 线程 4
WaitPulse2 干自己的事
WaitPulse1: 修改总数 4 线程 3
WaitPulse1: 干自己的事
WaitPulse2: 修改总数 5 线程 4
WaitPulse2 干自己的事
WaitPulse1: 修改总数 6 线程 3
WaitPulse1: 干自己的事
WaitPulse2: 修改总数 7 线程 4
WaitPulse2 干自己的事
WaitPulse1: 修改总数 8 线程 3
WaitPulse1: 干自己的事
WaitPulse1: 退出线程 3
WaitPulse2: 修改总数 9 线程 4
WaitPulse2: 退出线程 4
建锁原则

尽可能小范围、尽可能私有化、

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//非静态锁举例
public class Test
{
private object syncHandle; //锁 私有化

private object GetSyncHandle() //私有
{
//创建锁的时候避免其他线程闯入
System.Threading.Interlocked.CompareExchange(ref syncHandle, new Object(), null);
return syncHandle;
}

public void Method()
{
lock (GetSyncHandle())
{

}
}
}
不建议在加锁区域调用未知方法

未知方法如果是在另外的线程执行,且需要当前区域的锁了的锁,会死锁,罕见奇特的死锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();

var thread = new Thread(() =>
{
WorkerClass workerClass = new WorkerClass();
workerClass.RaiseProgress += RaiseProgress;
workerClass.DoWork();
});
thread.Start();
}

public void RaiseProgress(object sender, EventArgs eventArgs)
{
WorkerClass workerClass = sender as WorkerClass;
if (workerClass != null)
{
Action action = () =>
{
button1.Text = workerClass.Progress.ToString(); //UI线程 Progress要获取锁handle,
};
button1.Invoke(action);
}s
}

public class WorkerClass
{
public event EventHandler<EventArgs> RaiseProgress;

private object syncHandle = new object();

public void DoWork()
{
lock (syncHandle) //后台线程 锁住了handle
{
System.Threading.Thread.Sleep(1000);
progress++;
RaiseProgress?.Invoke(this, EventArgs.Empty); //死锁了,后台线程等UI线程执行完,UI线程又等后台线程的锁释放。
}
}

private int progress;

public int Progress
{
get
{
lock (syncHandle)
return progress++;
}
}
}
}

参考书:《Effective C#》