在解释这个话题前,我们先看看同步的程序,就是我们常用的Hello World 程序.
Code 1:

2

3

4

5

6

Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
22

23

24

图1
我们可以从图1看出,我们平常写的Hello 程序是同一个线程的,而且不是线程池理的线程程序.
按照上面的程序稍做改动, 那么开始我们第一个异步的Hello World 程序.
使用.Net 的委托机制来为我们的程序提供异步操作行为.
1步, 为我们的AsyncTest(Hello方法) 声明一个委托
public delegate string AsyncEventHandler(string name);
2步,使用委托提供的BeginInvoke, EndInvoke 方法(具体使用下一篇文章详细介绍)来提供异步的调用.
string val = test.Hello("Andy Huang");
修改为
AsyncEventHandler async = test.Hello;
IAsyncResult result = async.BeginInvoke("Andy Huang", null, null);
string val = async.EndInvoke(result);
下面是完整的代码.
Code 2:

2

3

4

5

6

Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
31

32

33

注释1: 用=操作符在于分配委托对象时候不需要初始化;并且异步调用时候只能有一个目标方法.
图2
对比图1 和图2, ,可以看出(2,Thread ID:#10,Is PoolThread?True),在使用异步机制的时候,其实就是开启一个新的线程来执行我们的方法,并且这个线程来自 线程堆. 到这里,我们就很好的解释了”什么是.Net异步机制?”
说到这里,结束了吗? 可能大家会想其实使用异步就是多了个委托( public delegate string AsyncEventHandler(string name);) 那么到底为我们做了些什么呢?
通过反编译(微软提供的IL Disassembler)我们看到
图3
编译器为我们生成了下面类定义(其实委托就是一个类)
Code 3
public sealed class AsyncEventHandler : MulticastDelegate
{
public AsyncEventHandler(object @object, IntPtr method)
{....}
public virtual IAsyncResult BeginInvoke(string name, AsyncCallback callback, object @object)
{...}
public virtual string EndInvoke(IAsyncResult result)
{...}
public virtual string Invoke(string name)
{...}
}
继承于MulticastDelegate, 提供了BeginInvoke / EndInvoke / Invoke.
Invoke 是我们Code 1 同步调用的方法,当然我们也可以直接使用Invoke来同步调用.
BeginInvoke / EndInvoke 是Code 2中使用的异步调用的方法,下篇文章我会详细介绍
什么时候使用.Net异步机制呢?
异步操作通常用于执行完成时间可能较长的任务,如打开大文件、连接远程计算机或查询数据库。异步操作在主应用程序线程以外的线程中执行。应用程序调用方法异步执行某个操作时,应用程序仍然可以继续执行当前的程序。
.NET Framework 的许多方面都支持异步编程功能,这些方面包括:
· 文件(File) IO、流(Stream) IO、套接字(Socket) IO。
· 网络。
· 远程处理信道(HTTP、TCP)和代理。
· 使用 ASP.NET 创建的 XML Web services。
· ASP.NET Web 窗体。
· 使用 MessageQueue 类的消息队列。
以上有word 文档直接粘贴,排版可能不太看,你可以通过下面来下载相应的代码/文档
1, 代码
什么是.Net的异步机制(Invoke,BeginInvoke,EndInvoke) - step 2
上一篇文章( 什么是.Net的异步机制(委托Delegate) - step 1) 中, 我已经解释了什么是异步编程, 那么现在我们就开始具体的说怎样异步编程.
我们怎样进行异步编程/开发?
现在扩充下上篇文章的类(AsyncTest),提供更多的例子并从中做下简单的对比, 从新的认识下异步的内部机制,下面我们增加一个新的委托
1步,我们添加一个新方法(计算年薪YearlySalary)
public decimal YearlySalary(decimal salary, int monthCount, decimal bonus);
2步,为这个方法增加异步的功能,这样我们仍然使用委托(Delegate)
public delegate decimal SalaryEventHandler(decimal salary, int monthCount, decimal bonus);
经过简单修改后,下面是我们新的AsyncTest类
Code1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

这里用.NET Reflector 5 来反编译,之所以用这个,因为比微软的会更加清晰明了.如果想了解这个工具的朋友可查看(http://reflector.red-gate.com/)
图1
开始我先对图1中的小图标进行个简单的解释
= 类(Class)
= 类继承的基类
= sealed(委托)
= 类的构造函数
= 方法
= virtual方法
下面我们先比较下SalaryEventHandler与 AsyncEventHandler委托的异同.
1) SalaryEventHandler
public delegate decimal SalaryEventHandler(decimal salary, int monthCount, decimal bonus);
图2.1
编译器生成的类Code2.1(图2.1)
Code 2.1

2

3

4


5

AsyncCallback callback, object @object)
6


7

8


9

10


11

2) AsyncEventHandler
public delegate string AsyncEventHandler(string name);
图2.2
编译器生成的类Code2.2(图2.2)
Code2.2

2

3

4


5

6


7

8


9

10


11

对比两个委托(事实上是一个sealed 的类),都继承于System.MuliticaseDelegate, 三个virtual的 Invoke / BeginInvoke / EndInvoke 方法.
//同步方法
Invoke : 参数的个数,类型, 返回值都不相同
//异步方法,作为一组来说明
BeginInvoke : 参数的个数和类型不同,返回值相同
EndInvoke : 参数相同,返回值不同
这里我们先介绍下 Invoke这个方法, 我们用SalaryEventHandler委托为例(直接调用Code 1 的类)
Code 3

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

输出的结果
图3
从结果可以看出,他们是同一个线程调用的(都是#10).这就说明[1],[2],[3]是同步调用
[2],[3]对比[1], 只不过[2],[3]是通过委托的方式(其实我们可以说成“通过代理的方式完成”),[1]是直接的调用.举一个我们平常生活中例子:买机票,我们到代理点购买机票而不是直接跑到机场购买,就好像我们叫别人帮我们买机票一样,最后到手的机票是一样的, SalaryEventHandler就是我们的代理点.所以用”代理”的方式还是直接调用的方式,他们提供的参数和返回值必须是一样的.
接下来我们开始讲异步机制核心的两个方法BeginInvoke/EndInvoke,他们作为一个整体来完成Invoke方法的调用,不同于Inoke方法的是他们是异步执行(另外开一个线程执行)的,下面先解释下他们的作用
BeginInvoke : 开始一个异步的请求,调用线程池中一个线程来执行
EndInvoke : 完成异步的调用, 处理返回值 和 异常错误.
注意: BeginInvoke和EndInvoke必须成对调用.即使不需要返回值,但EndInvoke还是必须调用,否则可能会造成内存泄漏.
我们来对比下 SalaryEventHandler与 AsyncEventHandler委托反编译BeginInoke后的异同.
SalaryEventHandler 委托:
public virtual IAsyncResult BeginInvoke(decimal salary, int monthCount, decimal bonus, AsyncCallback callback, object @object)
AsyncEventHandler 委托:
public virtual IAsyncResult BeginInvoke(string name, AsyncCallback callback, object @object)
可以看出参数的个数和类型是不同的,我们把焦点放到他们的相同点上,
1,返回值是相同的: 返回IAsyncResult 对象(异步的核心). IAsyncResult是什么呢? 简单的说,他存储异步操作的状态信息的一个接口,也可以用他来结束当前异步.具体的可以看下 http://msdn.microsoft.com/zh-cn/library/system.iasyncresult(VS.80).aspx
2,编译器会根据委托的参数个数和类型生成相应的BeginInvoke方法,只有最后两个参数是永远相同的,他提供一个AsyncCallback 委托(public delegate void AsyncCallback(IAsyncResult ar);) 和一个 Object 对象.
我们再来看看EndInvoke的异同.
SalaryEventHandler 委托:
public virtual decimal EndInvoke(IAsyncResult result)
AsyncEventHandler 委托:
public virtual string EndInvoke(IAsyncResult result)
EndInvoke的参数是一样的, 唯一是在是返回值不同(他们会根据自己委托的返回值类型生成自己的类型)
好,下面我会通过例子来说明BeginInvoke/EndInvoke,还是使用SalaryEventHandler委托为例(直接调用Code 1 的类)
.Net Framework 提供了两种方式来使用异步方法
第一种: 通过IAsyncResult 对象
Code 4.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

第二种: 通过回调函数. 使用倒数第二个参数AsyncCallback 委托(public delegate void AsyncCallback(IAsyncResult ar);) ,建议使用这种方法.
Code 4.2

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

BeginInvoke最后一个参数是做什么的呢?我把Code 4.2 方法修改下.
Code 4.3

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

异步的异常处理
接下来再讲讲EndInvoke,获取最后的返回值之外,他的一个重要的应用在”引发异常来从异步操作返回异常”
Code 5

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

我们主动在YearlySalary方法中引发异常,BeginInvoke开始异步调用的时候捕获到了这个异常,.Net Framework会在EndInvoke得到原始的异常.
说到这里,大家是否可以简单的应用委托来开始自己的异步操作呢? 下面看看我是怎样为我自己的类添加异步的.
第1步, 类的定义,需要遵循.Net Framework 的规则
1)同步和异步是同时并存的
2)从最上面的两个委托SalaryEventHandler与 AsyncEventHandler生成的BeginInvoke / EndInvoke 对比中看出,我们也来定义我们自己的异步方法,我们遵循微软设计师异步方法设计的规则,Begin+同步方法名 / End+同步方法名
BeginXXX 必须返回IAsyncResult对象,后两位参数必须为AsyncCallback callback, object state,前面的参数和同步方法的参数一样
EndXXX 参数必须为IAsyncResult对象,返回值为同步方法的返回值
Code 6.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

第2步: 调用我们编写的类
Code 6.2

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

附:Hello的方法的异步重构在下面的代码中可以下载到.
下一篇中我们会说说异步中的一些高级应用,异步的核心,还有微软.Net Framework 为我们提供的各种类中(具有异步方法的类),我们是怎么使用这些方法的.最后要提醒下大家:滥用异步,会影响性能,而且增加编程难度
以上有word 文档直接粘贴,排版可能不太看,你可以通过下面来下载相应的代码/文档
1,文档
2,代码 (VS2008开发,.Net Framework 2.0(C Sharp)编写)
什么是.Net的异步机制(APM核心:IAsyncResult) - step 3
在上一篇文章(什么是.Net的异步机制(Invoke,BeginInvoke,EndInvoke) - step 2 ), 我们已经简单介绍了异步的调用方式, 下面我们来看看异步的核心.
异步的核心: IAsyncResult
Asynchronous Programming Model
整个异步调用过程中都是围绕IAsyncResult来进行的,大家可以看看上篇文章的例子,BeginXXX 返回这个对象,EndXXX接收这个对象来结束当前异步对象,下面我们来看看IAsyncResult 接口成员/和实现此接口的AsyncResult类成员(其中有些在上篇中已经涉及到)
IAsyncResult接口

2

3

4

5

6

7

AsyncResult类

2

3

4

5

6

7

8

9

10

11

12

注意:基本上都是只读属性
下面我们来看看异步的执行顺序,并回顾下 IAsyncResult 下各个属性的应用,如果还是不熟悉请看前2篇文章.
Code 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

(MyThirdAsyncCode.AsyncTest.SalaryEventHandler)obj.AsyncDelegate;
32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

图1
我们看到DoAntherJob 比异步YearlySalary快2秒,看代码中(注1)和(注2),两个线程的执行结果
接下来,我们说说AsyncWaitHandle 属性. 他返回WaitHandle对象(System.Threading.WaitHandle), 他有3个重要的方法. WaitOne / WaitAny / WaitAll ,我们先来说下WaitOne,在Code1代码基础上只是增加了下面红色部分.
1,WaitOne
Code 1.1
IAsyncResult ar = del.BeginInvoke(100000, 15, 100000, callback, 2000);
//阻碍当前线程,直到异步调用结束.
ar.AsyncWaitHandle.WaitOne();
//开始其他工作.
DoAntherJob();
图1.1
执行输出,对比图1我们可以看到执行的次序不一样了(看时间),调用WaitOne,会阻碍当前线程,直到异步完成,才释放当前的线程, WaitOne 提供了时间的重载版本WaitOne(int millisecondsTimeout)/ WaitOne(TimeSpan timeout);来判断阻碍的时间.无参的版本是无限等待的(直到异步调用结束)
2, WaitAll
我们在Code1的代码基础上加上Hello的异步调用(使Main提供多个异步调用),注意红色部分.
Code 1.2

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

(MyThirdAsyncCode.AsyncTest.SalaryEventHandler)obj.AsyncDelegate;
32

33

34

35

36

37

38

39

40

41

42

43

44

(MyThirdAsyncCode.AsyncTest.AsyncEventHandler)obj.AsyncDelegate;
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

图1.2
从图1.2中可以看出,WaitAll会阻碍当前线程(主线程#10),等待所有异步的对象都执行完毕(耗时最长的异步),才释放当前的线程,WaitAll/WaitAny的重载版本和WaitOne一样.
3, WaitAny
和WaitAll 基本上是一样的.我们可以使用 WaitAny 来指定某个/某几个委托先等待,修改Code1.2红色部分,使用WaitAny.
Code1.3
//把salayAsyc异步的句柄保存到WaitHandle 对象中
WaitHandle[] handles = { salayAsyc.AsyncWaitHandle };
//阻碍当前线程,直到所有异步调用结束.
WaitHandle.WaitAny(handles);
图1.3
我们阻碍了DoAntherJob(#10)线程,直到Salary异步调用计算完成.同样我们可以巧用这三个方法来改变我们方法执行的顺序.
释放资源
Code2

2

3

4

5

(MyThirdAsyncCode.AsyncTest.SalaryEventHandler)obj.AsyncDelegate;
6

7

8

9

当开始调用BeginXXX后,就会创建一个新的AsyncResult对象.这个对象会构造一个WaitHandle句柄(通过AsyncWaitHandle访问),当我们EndXXX后,并不会马上关闭这个句柄,而是等待垃圾收集器来关闭,这时候我们最后在调用EndXXX完成后,显示的关闭这个句柄.
说到这里,我们基本上把异步方法都解释一遍,下面我们来看看重构的异步对象,我们也可以细细体会异步对象的内部执行代码..下面Code3.1/3.2/3.3代码来自Jeffery Richard大师的Power Threading类库,具体可查看http://msdn.microsoft.com/en-us/magazine/cc163467.aspx
重构的异步对象
1步,构造一个内部无参的AsyncResultNoResult对象,继承IAsyncResult接口(保留原创的注释)
Code3.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

115

2步,继承AsyncResultNoResult对象,并且为他提供返回值和泛型的访问
Code3.2

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

3步,模拟长时间的异步工作
Code3.3

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

来自Jeffrey Richter大师更多更详细的异步操作方法, 请查看http://www.wintellect.com /PowerThreading.aspx,对于一些朋友可能看不懂Code3.1-3.3代码(其实没什么所谓的),因为涉及到过多的线程知识,这里出 于让你获得更多的更深层次的(异步)认识为目的,才提供上面代码,在以后的文章会再次探讨.
下一篇章中,我们来看看微软提供有异步调用的类是如何调用的,并从中我会给出些真实应用环境中的一些小技巧,让你编写的代码更健壮更完善.
以上有word 文档直接粘贴,排版可能不太看,你可以通过下面来下载相应的代码/文档
1,文档
2,代码(VS2008开发,.Net Framework 2.0(C Sharp)编写)
什么是.Net的异步机制(异步Stream读/写) - step 4
在前面3篇文章,我已经介绍了异步的工作原理和相关方法和参数的应用.下面我们介绍Stream流的操作, 并使用System.IO.FileStream来揭开如何开发异步的Stream(System.IO.Stream) 读/写操作
异步的Stream读/写操作
下面是继承于System.IO.Stream的类
System.IO.Stream
Microsoft.JScript.COMCharStream
System.IO.BufferedStream
System.IO.FileStream
System.IO.MemoryStream
System.IO.UnmanagedMemoryStream
System.Security.Cryptography.CryptoStream
System.Printing.PrintQueueStream
System.IO.Pipes.PipeStream
System.Data.OracleClient.OracleBFile
System.Data.OracleClient.OracleLob
System.IO.Compression.DeflateStream
System.IO.Compression.GZipStream
System.Net.Sockets.NetworkStream
System.Net.Security.AuthenticatedStream
在System.IO.Stream中提供了异步的读/写(Read/Write)行为,上面继承于System.IO.Stream的类都具有同样的异步操作行为.在.Net Framework框架中,微软设计师使用Begin+同步方法名 / End+同步方法名来设计异步方法的规则,基本上我们在微软MSDN看到的 BeginXXX + EndXXX都是异步的方法,并且当我们在某个类中看到BeginInvoke / EndInvoke,都是微软提供的最原始的异步方法.在System.IO.Stream类中表现为BeginRead+EndRead / BeginWrite/EndWrite.
我们来看一个例子,FileStream(System.IO),Read / BeginRead+EndRead,读取文件内容,开始我们使用同步方法.
同步调用
Code1.1
2 {
3 static string path = @"c:/file.txt";//确保你本地有这个文件
4 const int bufferSize = 5;//演示,一次只读取5 byte
5 static void Main()
6 {
7 FileStream fs = new FileStream(path, FileMode.Open,
8FileAccess.Read, FileShare.Read, 20480, false);//同步调用false
9 using (fs)//使用using来释放FileStream资源
10 {
11 byte[] data = new byte[bufferSize];
12 StringBuilder sb = new StringBuilder(500);
13 int byteReads;
14 do// 不断循环,直到读取完毕
15 {
16 byteReads = fs.Read(data, 0, data.Length);
17 sb.Append(Encoding.ASCII.GetString(data, 0, byteReads));
18 } while (byteReads > 0);
19 Console.WriteLine(sb.ToString());//输出到工作台
20
21 }//自动清除对象资源,隐式调用fs.Close();
22 Console.ReadLine();// 让黑屏等待,不会直接关闭..
23 }
24 }
方法非常简单,它会构造一个 FileStream 对象,调用 Read方法,不断循环读取数据。C# using 语句可确保完成数据处理后会关闭该 FileStream 对象。
下面我们看异步调用(BeginRead/EndRead)
异步调用
Code1.2
2 {
3 static string path = @"c:/file.txt";//确保你本地有这个文件
4 const int bufferSize = 5;//演示,一次只读取5 byte
5 static byte[] data;
6 static void Main()
7 {
8 data = new byte[bufferSize];
9 FileStream fs = new FileStream(path, FileMode.Open,
10FileAccess.Read, FileShare.Read, 20480, true);//设置异步调用true, 注意0
11
12 //异步读取文件,把FileStream对象作为异步的参数// <-
13 AsyncCallback callback = new AsyncCallback(OnReadCompletion);
14 IAsyncResult async = fs.BeginRead(data, 0, bufferSize, callback, fs); // <-
15
16 Console.ReadLine();// 让黑屏等待,不会直接关闭..
17 }
18 static void OnReadCompletion(IAsyncResult asyncResult)
19 {
20 FileStream fs = asyncResult.AsyncState as FileStream;
21 int bytesRead = fs.EndRead(asyncResult);
22 //输出到工作台
23 Console.Write(Encoding.ASCII.GetString(data, 0, bytesRead));
24 //不断循环,直到读取完毕
25 if (bytesRead > 0)
26 fs.BeginRead(data, 0, bufferSize, OnReadCompletion, fs);
27 else
28 fs.Close(); //当全部读取完毕,显式释放资源
29 }
30 }
方法是使用BeginRead和EndRead 完成的, 我们注意到方法不能使用 C# using 语句(释放资源),因为 FileStream 是在一个主线程中打开,然后在另一个线程中关闭的,而是通过把FileStream 作为参数的形式来在另外一个线程中关闭的(fs.Close();),查看红色部分.
注意0:创建FileStram 对象,如果没有FileStream fs = new FileStream(path, FileMode.Open,FileAccess.Read, FileShare.Read, 20480, true); bool useAsync=true 或者构造FileStream(String, FileMode, FileAccess, FileShare, Int32, FileOptions) FileOptions = FileOptions.Asynchronous 时, 即使使用了BeginRead/EndRead, 程序也是在同步执行方法,FileStream 对象会使用其他线程来模仿异步行为,反而降低了应用程序的性能.
下面我将通过使用C# 匿名方法(C# 2.0) 和 lambda 表达式(C# 3.0引入的一个新功能) 来完成上面操作,如果对这个不熟悉的朋友可以查看下面文章.
匿名方法:http://www.microsoft.com/china/msdn/library/langtool/vcsharp/CreElegCodAnymMeth.mspx?mfr=true
lambda 表达式:http://msdn.microsoft.com/zh-cn/magazine/cc163362.aspx
C# 匿名方法 和 lambda 表达式
1,匿名方法:
Code1.3
2 {
3 static string path = @"c:/file.txt";//确保你本地有这个文件
4 const int bufferSize = 5;//演示,一次只读取5 byte
5 static void Main()
6 {
7 byte[] data = new byte[bufferSize];
8 //[1]
9 FileStream fs = new FileStream(path, FileMode.Open,
10FileAccess.Read, FileShare.Read, 20480, true);//设置异步调用true
11 //使用匿名委托方式
12 AsyncCallback callback = null; //注意1
13 callback = delegate(IAsyncResult asyncResult)//匿名方法
14 {
15 int bytesRead = fs.EndRead(asyncResult);//[2]
16 Console.Write(Encoding.ASCII.GetString(data, 0, bytesRead));//输出到工作台
17 //不断循环,直到读取完毕
18 if (bytesRead > 0)
19 fs.BeginRead(data, 0, bufferSize, callback, null);//[3]
20 else
21 fs.Close();//[4]
22 };
23
24 //异步读取文件
25 IAsyncResult async = fs.BeginRead(data, 0, bufferSize, callback, null);
26
27 Console.ReadLine();// 让黑屏等待,不会直接关闭..
28 }
29 }
对比Code1.2代码我们可以看出, 匿名方法非常出色的完成我们功能, 在匿名方面体内 fs ([2][3][4])像普通变量一样执行引用FileStream([1]) 对象,而不需要任何的类型转换. 对象在方法之间轻松实现传递,并且从一个线程轻松迁移到另一个线程, 对APM 编程而言这是十分完美的,但实际上编译器会重新编写您的代码,从堆栈中取出这些变量,并将它们作为字段嵌入对象。由于编译器会自动执行所有的工作,您可以很轻松地将最后一个参数的空值传递到 BeginRead 方法,因为现在没有必要再在方法和线程之间显式传递的数据了。
注意1: 必须先AsyncCallback callback = null; 要不编程器会告诉你错误:” Use of unassigned local variable 'callback '”.
2,Lambda 表达式
我们只需要简单的修改(在执行同一操作,lambda 表达式语法比 C# 匿名方法更简洁),匿名方法,把Code1.3中红色部分的
callback = delegate(IAsyncResult asyncResult)
修改成
callback = asyncResult =>
下面是完整代码.
Code1.4
static class Program
{
static string path = @"c:/file.txt";//确保你本地有这个文件
const int bufferSize = 5;//演示,一次只读取5 byte
static void Main()
{
byte[] data = new byte[bufferSize];
FileStream fs = new FileStream(path, FileMode.Open,
FileAccess.Read, FileShare.Read, 20480, true);//设置异步调用true
//使用lambda 表达式
AsyncCallback callback = null;//注意1
callback = asyncResult =>
{
int bytesRead = fs.EndRead(asyncResult);
Console.Write(Encoding.ASCII.GetString(data, 0, bytesRead));//输出到工作台
//不断循环,直到读取完毕
if (bytesRead > 0)
fs.BeginRead(data, 0, bufferSize, callback, null);
else
fs.Close();
};
//异步读取文件
IAsyncResult async = fs.BeginRead(data, 0, bufferSize, callback, null);
Console.ReadLine();// 让黑屏等待,不会直接关闭..
}
}
最后,我们来看看异步的写操作(BeginWrite/EndWrite)
Code2
2 {
3 static void Main()
4 {
5 FileStream fs = new FileStream("text.txt", FileMode.Create,
6FileAccess.ReadWrite, FileShare.None, 20480, true);//设置异步调用true
7 //输入信息
8 Console.Write("Please Enter:");
9 byte[] data = Encoding.ASCII.GetBytes(Console.ReadLine());
10
11 //异步写文件
12 IAsyncResult async = fs.BeginWrite(data, 0, data.Length, asyncResult =>
13 {
14 fs.EndWrite(asyncResult);//写文件介绍,输出到text.txt文件中.
15 fs.Close();
16
17 }, null);
18
19 Console.ReadLine();// 让黑屏等待,不会直接关闭..
20 }
21 }
大家觉得是否很简单呢? 基本上所有具有异步行为的流(继承于System.IO.Stream)操作都可以按照类似于上面的代码编写. 当然其他异步行为也可以使用上面代码中的技巧. 在System.IO.Stream 中,提供了ReadTimeout/WriteTimeout 的超时处理,但是基类中是不支持的.会报 InvalidOperationException 异常,反编译可以看到throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_TimeoutsNotSupported")).
下篇文章我会提供其他的例子来说明异步中的线程间通信.采用Window Forms程序.
以上有word 文档直接粘贴,排版可能不太好看,你可以通过下面来下载相应的代码/文档
1,文档
2,代码(VS2008开发,.Net Framework 3.5(C Sharp)编写)
什么是.Net的异步机制(线程间通信) - step 5
前几篇文章我已经对异步的操作进行的详细的解释.异步操作也是线程的一种,当我们开始一个异步操作(新线程),完成调用后需要和其他线程通信(可能需要告知状态信息),这时候我们就需要线程间的通信编程.
线程间通信
我们看下面的图
图1
我们来看线程间通信的原理:线程(Thread B)和线程(Thread A)通信, 首先线程A 必须实现同步上下文对象(Synchronization Context), 线程B通过调用线程A的同步上下文对象来访问线程A,所有实现都是在同步上下文中完成的.线程B有两种方式来实现线程间的通信.
第一种:调用线程A的同步上下文对象,阻碍当前线程,执行红色箭头调用,直到黄色箭头返回(同步上下文执行完毕)才释放当前线程. (1->2->3->5)
第二种: 调用线程A的同步上下文对象(实际上是在开启一个新线程去执行,1->2->3->5) ,执行红色箭头,但并不阻碍当前线程(原有线程,1->4->5),绿色箭头继续执行.
文章中将会通过下面几个类来进行介绍:
1. ISynchronizeInvoke
接口
2. SynchronizationContext 类
3. AsyncOperation / AsyncOperationManager 类
附: 同步上下文对象(Synchronization Context)是什么呢? 当我们访问上下文对象时候(多个对象可以存在于一个上下文中), 是使用代理的方式引用的,而不是直接引用的.这种方式可能是当多个对象访问上下文对象时候,先到达对象先访问,锁住,执行完毕再解锁,排队式访问.
1. ISynchronizeInvoke
接口
我们先来看下面一段异步的代码(Window Form控件下有1个Button/1个Label),但点击Button的时候,执行异步调用,完成后,告诉Window Form的 Label控件Text属性” Asynchronous End”.
Code1.1
2 private void button1_Click(object sender, EventArgs e)
3 {
4 //辅助方法,查看当前线程
5 Debug.WriteLine(string.Format("Window Form Method.Thread ID:#{0}",
Thread.CurrentThread.ManagedThreadId));
6 //Label lblStatus 属于主线程的控件[1]
7 this.lblStatus.Text = "Asynchronous Start.";
8 //使用委托来调用异步方法
9 DoWork work = DoWorkMethod;
10 work.BeginInvoke(OnWorkCallback, work);
11 }
12 void OnWorkCallback(IAsyncResult asyncResult)
13 {
14 //辅助方法,查看当前线程
15 Debug.WriteLine(string.Format("Asynchronous Callback Method.Thread ID:#{0}",
Thread.CurrentThread.ManagedThreadId));
16 DoWork work = asyncResult.AsyncState as DoWork;
17 if (work != null)
18 {
19 work.EndInvoke(asyncResult);
20 }
21 // 报错:"线程间操作无效: 从不是创建控件“lblStatus”的线程访问它."
22 this.lblStatus.Text = "Asynchronous End"; //上面注释[1]
23 }
24
25 void DoWorkMethod()
26 {
27 Thread.Sleep(3000);//模拟耗时工作
28 }
运行代码,我们在第22行报错(异步方法体内).为什么呢?我们必须清楚的一点,在windows应用窗体应用程序中,对窗体上控件属性的任何修改都必须在主线程中完成。不能从其他线程安全地访问控件的方法和属性。从Debug窗口中我们也可以看出(图1.1).执行Button Click事件的时候,运行在线程ID =#10; 在异步的方法体内,运行在线程ID=#7.不同线程间不能直接通信.
图1.1
为了解决这个问题,实现图1.1 中 #10 和 #7 的通信,下来开始认识ISynchronizeInvoke
接口(此接口来自.Net Framework 1.0),提供3个方法1个属性:
BeginInvoke / EndInvoke 方法 : 异步方法
Invoke 方法 : 同步方法
InvokeRequired 属性 : 判读来源的执行线程
下面我们看Code1.2的具体代码来说明(对Code1.1改写,其中Label 改为ListBox)
Code1.2
2 private void button1_Click(object sender, EventArgs e)
3 {
4 //更新状态,添加到Listbox 中
5 AddValue("Asynchronous Start.");
6 //使用委托来调用异步方法
7 DoWork work = DoWorkMethod;
8 work.BeginInvoke(OnWorkCallback, work);
9 }
10
11 void OnWorkCallback(IAsyncResult asyncResult)
12 {
13 DoWork work = asyncResult.AsyncState as DoWork;
14 if (work != null)
15 {
16 work.EndInvoke(asyncResult);
17 }
18 //(1)方法:调用Control控件的Invoke
19 //Action<string> asyncUpdateState = UpdateStatus; //Action<string> 介绍=> 附1
20 //Invoke(asyncUpdateState, "1:Asynchronous End.");
21
22 //(2)方法:直接在异步调用的线程下
23 UpdateStatus("2:Asynchronous End.");
24 }
25
26 void UpdateStatus(string input)
27 {
28 //把你需要通知的控件Control 赋值给ISynchronizeInvoke
29 //来实现线程间的通信
30 ISynchronizeInvoke async = this.listBoxStatus;
31 //使用(1)方法,InvokeRequired == false ,来源当前(Window Form)主线程
32 if (async.InvokeRequired == false)
33 AddValue(input);
34 else// 使用(2)方法 == true ,来源其他线程(异步)
35 {
36 Action<string> action = new Action<string>(status =>
37 {
38 AddValue(status);
39 });
40 //调用ISynchronizeInvoke 提供的Invoke 同步方法,阻碍线程,直到调用结束
41 //也可以使用ISynchronizeInvoke 提供的异步BeginInvoke/EndInvoke方法来实现调用.
42 async.Invoke(action, new object[] { input });
43 }
44 }
45
46 void AddValue(string input)
47 {
48 this.listBoxStatus.Items.Add(string.Format("[(#{2}){0}]Context is null:{1}", input,Thread.CurrentContext==null, Thread.CurrentThread.ManagedThreadId));
49 }
50 void DoWorkMethod()
51 {
52 Thread.Sleep(3000);//模拟耗时工作
53 }
图1.2
在代码中(UpdateStatus方法体内),我们可以看到主要是在ISynchronizeInvoke async = this.listBoxStatus;实现了线程间的通信,MSDN的解释” 实现此接口的对象可以接收事件已发生的通知,并且可以响应有关该事件的查询”. 并使Window Form(主线程) 下的ListBox 控件和来自异步方法(另外一个线程)的建立了通道. InvokeRequired 判断线程的来源,如果使用(1)方法,来源于Window Form 自身Control 的Invoke方法, InvokeRequired将返回false; 来源另外线程(异步)如果使用(2)返回true.同时ISynchronizeInvoke 提供了异步(BeginInvoke+EndInvok)和同步方法(Invoke)来实现线程间通信.Invoke 就是最上面的图1 所示的第一种 / BeginInvoke+EndInvok 是第二种.
附1:关于Action<T…> / Func (T…, TResult) (简单的说就是”简化后的委托”)