什么是.Net异步机制呢?

 

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

 

Code 1:

 1      class  Program
 2      {
 3        static void Main(string[] args)
 4        {
 5            // 查看当前的线程ID, 是否线程池里面的线程
 6            Console.WriteLine("1,Thread ID:#{0},Is PoolThread?{1}",
Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
 7
 8            AsyncTest test = new AsyncTest();
 9            string val = test.Hello("Andy Huang");
10
11            Console.WriteLine(val);
12            Console.ReadLine(); // 让黑屏等待,不会直接关闭..
13        }

14    }

15
16      public   class  AsyncTest
17      {
18        public string Hello(string name)
19        {
20            // 查看当前的线程ID, 是否线程池里面的线程
21            Console.WriteLine("2,Thread ID:#{0},Is PoolThread?{1}",
Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
22            return "Hello:" + name;
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:

 1      class  Program
 2      {
 3        static void Main(string[] args)
 4        {
 5            // 查看当前的线程ID, 是否线程池里面的线程
 6            Console.WriteLine("1,Thread ID:#{0},Is PoolThread?{1}",
Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
 7
 8            AsyncTest test = new AsyncTest(); 
 9            //把Hello 方法分配给委托对象
10            AsyncEventHandler async = test.Hello; //注释1,建议用=,不用+=
11
12            //发起一个异步调用的方法,赋值"Andy Huang", 返回IAsyncResult 对象
13            IAsyncResult result = async.BeginInvoke("Andy Huang"nullnull);
14
15            //这里会阻碍线程,直到方法执行完毕
16            string val = async.EndInvoke(result);
17
18            Console.WriteLine(val);
19            Console.ReadLine(); // 让黑屏等待,不会直接关闭..
20        }

21    }

22
23      // 我们使用委托来提供.Net的异步机制
24      public   delegate   string  AsyncEventHandler( string  name);
25      public   class  AsyncTest
26      {
27        public string Hello(string name)
28        {
29            // 查看当前的线程ID, 是否线程池里面的线程
30            Console.WriteLine("2,Thread ID:#{0},Is PoolThread?{1}",
Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
31            return "Hello:" + name;
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, 代码

2,原始文档(doc)

 

 

什么是.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

 1 // 我们使用委托来提供.Net的异步机制
 2 public   delegate   string  AsyncEventHandler( string  name);  //  对应Hello 方法
 3 public   delegate   decimal  SalaryEventHandler( decimal  salary,  int  monthCount,  decimal  bonus);  //  对应YearlySalary方法
 4 public   class  AsyncTest
 5 {
 6    public string Hello(string name)
 7    {
 8        return "Hello:" + name;
 9    }

10
11    /// <summary>
12    /// 计算一年的薪水
13    /// </summary>
14    /// <param name="salary">月薪</param>
15    /// <param name="monthCount">一年支付月数量</param>
16    /// <param name="bonus">奖金</param>
17    /// <returns></returns>

18    public decimal YearlySalary(decimal salary, int monthCount, decimal bonus)
19    {
20        //添加辅助方法,查看当前的线程ID
21        Console.WriteLine("Thread ID:#{0}", Thread.CurrentThread.ManagedThreadId);
22
23        return salary * monthCount + bonus;
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

 1      public   sealed   class  SalaryEventHandler : MulticastDelegate
 2      {
 3        public SalaryEventHandler(object @object, IntPtr method)
 4        {.}
 5        public virtual IAsyncResult BeginInvoke(decimal salary, int monthCount, decimal bonus,
 AsyncCallback callback, 
object @object)
 6        {}
 7        public virtual decimal EndInvoke(IAsyncResult result)
 8        {}
 9        public virtual decimal Invoke(decimal salary, int monthCount, decimal bonus)
10        {}
11    }

2)      AsyncEventHandler

public delegate string AsyncEventHandler(string name);

2.2

编译器生成的类Code2.2(2.2)

Code2.2

 1      public   sealed   class  AsyncEventHandler : MulticastDelegate
 2      {
 3        public AsyncEventHandler(object @object, IntPtr method)
 4        {.}
 5        public virtual IAsyncResult BeginInvoke(string name, AsyncCallback callback, object @object)
 6        {}
 7        public virtual string EndInvoke(IAsyncResult result)
 8        {}
 9        public virtual string Invoke(string name)
10        {}
11    }

 

对比两个委托(事实上是一个sealed 的类),都继承于System.MuliticaseDelegate, 三个virtual Invoke / BeginInvoke / EndInvoke 方法.

//同步方法

Invoke : 参数的个数,类型, 返回值都不相同

 

 

//异步方法,作为一组来说明

BeginInvoke : 参数的个数和类型不同,返回值相同

EndInvoke : 参数相同,返回值不同

 

这里我们先介绍下 Invoke这个方法, 我们用SalaryEventHandler委托为例(直接调用Code 1 的类)

Code 3

 1 class  Program
 2 {
 3    static void Main(string[] args)
 4    {
 5        //添加辅助方法,查看当前的线程ID
 6        Console.WriteLine("Main Thread ID:#{0}", Thread.CurrentThread.ManagedThreadId);
 7
 8        AsyncTest test = new AsyncTest();
 9        //[1],我们习惯的调用方式
10        decimal v1 = test.YearlySalary(10000015100000);
11        //使用委托调用
12        SalaryEventHandler salaryDelegate = test.YearlySalary;
13        //[2],编译器会自动的把[2]转变成[3]Invoke的调用方式,[2]和[3]是完全相同的
14        decimal v2 = salaryDelegate(10000015100000);
15        //[3]
16        decimal v3 = salaryDelegate.Invoke(10000015100000);
17
18        Console.WriteLine("V1:{0},V2:{1},V3:{2}", v1, v2, v3);
19        Console.ReadLine(); // 让黑屏等待,不会直接关闭..
20    }

21}

输出的结果

3

从结果可以看出,他们是同一个线程调用的(都是#10).这就说明[1],[2],[3]是同步调用

[2],[3]对比[1], 只不过[2],[3]是通过委托的方式(其实我们可以说成“通过代理的方式完成”),[1]是直接的调用.举一个我们平常生活中例子:买机票,我们到代理点购买机票而不是直接跑到机场购买,就好像我们叫别人帮我们买机票一样,最后到手的机票是一样的, SalaryEventHandler就是我们的代理点.所以用代理的方式还是直接调用的方式,他们提供的参数和返回值必须是一样的.

 

接下来我们开始讲异步机制核心的两个方法BeginInvoke/EndInvoke,他们作为一个整体来完成Invoke方法的调用,不同于Inoke方法的是他们是异步执行(另外开一个线程执行),下面先解释下他们的作用

BeginInvoke : 开始一个异步的请求,调用线程池中一个线程来执行
EndInvoke : 完成异步的调用, 处理返回值 异常错误.

注意: BeginInvokeEndInvoke必须成对调用.即使不需要返回值,但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

 1 class  Program
 2 {
 3    static IAsyncResult asyncResult;
 4
 5    static void Main(string[] args)
 6    {
 7
 8        AsyncTest test = new AsyncTest();
 9        SalaryEventHandler dele = test.YearlySalary;
10        //异步方法开始执行,返回IAsyncResult(存储异常操作的状态信息) 接口,同时EndInvoke 方法也需要他来作为参数来结束异步调用
11        asyncResult = dele.BeginInvoke(10000015100000nullnull);
12        //获取返回值
13        decimal val = GetResult();
14        Console.WriteLine(val);
15        Console.ReadLine(); // 让黑屏等待,不会直接关闭..
16    }

17
18    static decimal GetResult()
19    {
20        decimal val = 0;
21        //获取原始的委托对象:先是获取AsyncResult对象,再根据他的AsyncDelegate属性来调用
当前 的(那一个)委托对象
22        AsyncResult result = (AsyncResult)asyncResult;
23        SalaryEventHandler salDel = (SalaryEventHandler)result.AsyncDelegate;
24
25        //调用EndInvoke获取返回值
26        val = salDel.EndInvoke(asyncResult);
27
28        return val;
29    }

30}

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

Code 4.2

 1 class  Program
 2 {
 3    static void Main(string[] args)
 4    {
 5        AsyncTest test = new AsyncTest();
 6        SalaryEventHandler dele = test.YearlySalary;
 7
 8        //异步方法开始执行,使用BeginInvoke 倒数第二个参数(AsyncCallback委托对象) ,而不用返回值
 9        dele.BeginInvoke(10000015100000, GetResultCallBack, null);
10        //和上面相同的
11        //AsyncCallback callback = new AsyncCallback(GetResultCallBack);
12        //dele.BeginInvoke(100000, 15, 100000, callback, null);
13
14        Console.ReadLine(); // 让黑屏等待,不会直接关闭..
15    }

16
17    //必须遵循AsyncCallback 委托的定义:返回值为空,一个IAsyncResult对象参数
18    static void GetResultCallBack(IAsyncResult asyncResult)
19    {
20        decimal val = 0;
21        //获取原始的委托对象
22        AsyncResult result = (AsyncResult)asyncResult;
23        SalaryEventHandler salDel = (SalaryEventHandler)result.AsyncDelegate;
24
25        //调用EndInvoke获取返回值
26        val = salDel.EndInvoke(asyncResult);
27
28        Console.WriteLine(val);
29    }

30}

 

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

Code 4.3

 1 class  Program
 2 {
 3    static void Main(string[] args)
 4    {
 5        AsyncTest test = new AsyncTest();
 6        SalaryEventHandler dele = test.YearlySalary;
 7
 8        //异步方法开始执行,看最后一个参数(Object对象) [Note1:],这里我们传递2000(int)
 9        dele.BeginInvoke(10000015100000, GetResultCallBack, 2000);
10
11        Console.ReadLine(); // 让黑屏等待,不会直接关闭..
12    }

13
14    static void GetResultCallBack(IAsyncResult asyncResult)
15    {
16        //[Note1:],他的作用就是来 "传递额外的参数",因为他本身是Object对象,我们可以传递任何对象
17        int para = (int)asyncResult.AsyncState;
18        Console.WriteLine(para);//输出:2000
19    }

20}

 

异步的异常处理

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

Code 5

 1 class  Program
 2 {
 3    static void Main(string[] args)
 4    {
 5        AsyncTest test = new AsyncTest();
 6        SalaryEventHandler dele = test.YearlySalary;
 7
 8        dele.BeginInvoke(10000015100000, GetResultCallBack, null);
 9        Console.ReadLine(); // 让黑屏等待,不会直接关闭..
10    }

11
12    static void GetResultCallBack(IAsyncResult asyncResult)
13    {
14        decimal val = 0;
15        //获取原始的委托对象
16        AsyncResult result = (AsyncResult)asyncResult;
17        SalaryEventHandler salDel = (SalaryEventHandler)result.AsyncDelegate;
18        try
19        {
20            //如果EndInvoke发生异常,会在EndInvoke得到原始的异常.
21            val = salDel.EndInvoke(asyncResult);
22            Console.WriteLine(val);
23        }

24        catch (Exception ex)
25        {
26            Console.WriteLine(ex.Message);
27        }

28    }

29}

30 public   delegate   decimal  SalaryEventHandler( decimal  salary,  int  monthCount,  decimal  bonus);  //  对应YearlySalary方法
31 public   class  AsyncTest
32 {
33    public decimal YearlySalary(decimal salary, int monthCount, decimal bonus)
34    {
35        throw new Exception("error"); //引发异常
36        return salary * monthCount + bonus;
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

 1 public   class  AsyncTest
 2 {
 3    private delegate string AsyncEventHandler(string name);
 4    private AsyncEventHandler _Async;
 5    public string Hello(string name)
 6    {
 7        return "Hello:" + name;
 8    }

 9
10    //按照.Net Framework的规则 ,编写我们自己的BeginInvoke方法
11    public virtual IAsyncResult BeginHello(string name, AsyncCallback callback, object state)
12    {
13        AsyncEventHandler del = Hello;
14
15        this._Async = del;
16
17        return del.BeginInvoke(name, callback, state);
18    }

19    //编写我们自己的EndInvoke方法
20    public virtual string EndHello(IAsyncResult asyncResult)
21    {
22        if (asyncResult == null)
23            throw new ArgumentNullException("asyncResult");
24        if (this._Async == null)
25            throw new ArgumentException("_Async");
26
27        string val = string.Empty;
28        try
29        {
30            val = this._Async.EndInvoke(asyncResult);
31        }

32        finally
33        {
34            this._Async = null;
35        }

36        return val;
37    }

38}

 

2: 调用我们编写的类

Code 6.2

 1 class  Program
 2 {
 3    static void Main(string[] args)
 4    {
 5        AsyncTest test = new AsyncTest();
 6        //使用回调函数,就是上面提到的"第二种"
 7        AsyncCallback callback = new AsyncCallback(OnHelloCallback);
 8        test.BeginHello("Andy Huang", callback, test);
 9        //和上面一样
10        //IAsyncResult result = test.BeginHello("Andy Huang", OnHelloCallback, test);
11
12        Console.ReadLine(); // 让黑屏等待,不会直接关闭..
13    }

14
15    static void OnHelloCallback(IAsyncResult asyncResult)
16    {
17        //获取额外的参数
18        AsyncTest obj = (AsyncTest)asyncResult.AsyncState;
19        string val = obj.EndHello(asyncResult);
20        Console.WriteLine(val);
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接口

1 public   interface  IAsyncResult
2      {
3        WaitHandle AsyncWaitHandle get; } //阻塞一个线程,直到一个或多个同步对象接收到信号
4        Boolean IsCompleted get; } //判读当前异步是否完成
5        Object AsyncState get; } //获取额外的参数值,请看上一篇文章的Code 4.3
6        Boolean CompletedSynchronously get; } //几乎没有使用
7    }

AsyncResult

 1      public   class  AsyncResult : IAsyncResult, IMessageSink
 2      {
 3        //IAsyncResult 的实现      
 4        public virtual WaitHandle AsyncWaitHandle get; }
 5        public virtual bool IsCompleted get; }
 6        public virtual object AsyncState get; }
 7        public virtual bool CompletedSynchronously get; }
 8
 9        // 其他一些重要的属性
10        public bool EndInvokeCalled getset; } //检验是否调用了EndInvoke()
11        public virtual object AsyncDelegate get; } //获取原始的委托对象,可查看上一篇文章中的Code 4.1/4.2/5
12    }

 

注意:基本上都是只读属性

下面我们来看看异步的执行顺序,并回顾下 IAsyncResult 下各个属性的应用,如果还是不熟悉请看前2篇文章.

Code 1:

 1     class  Program
 2      {
 3        static void Main(string[] args)
 4        {
 5            Console.WriteLine("[(#{1}){0}]:Asynchronous Start", DateTime.Now.ToString(), Thread.CurrentThread.ManagedThreadId);
 6
 7            AsyncTest test = new AsyncTest();
 8            MyThirdAsyncCode.AsyncTest.SalaryEventHandler del = test.YearlySalary;
 9            //使用回调函数
10            AsyncCallback callback = new AsyncCallback(OnSalaryCallback);
11            IAsyncResult ar = del.BeginInvoke(10000015100000, callback, 2000);
12
13            DoAntherJob();
14            Console.ReadLine(); // 让黑屏等待,不会直接关闭..
15        }

16
17        //开始其他工作.
18        static void DoAntherJob()
19        {
20            Thread.Sleep(1000);//需要1秒才能完成这个工作,注1
21            Console.WriteLine("[(#{1}){0}]:Do Another Job", DateTime.Now.ToString(), Thread.CurrentThread.ManagedThreadId);
22        }

23
24        static void OnSalaryCallback(IAsyncResult asyncResult)
25        {
26            //通过AsyncState 获取额外的参数.
27            decimal para = (int)asyncResult.AsyncState;
28
29            //通过AsyncDelegate 获取原始的委托对象
30            AsyncResult obj = (AsyncResult)asyncResult;
31            MyThirdAsyncCode.AsyncTest.SalaryEventHandler del =
(MyThirdAsyncCode.AsyncTest.SalaryEventHandler)obj.AsyncDelegate;
32
33            if (asyncResult.IsCompleted)// 判读是否已经调用完成
34                Console.WriteLine("[(#{1}){0}]:Asynchronous Finished.", DateTime.Now.ToString(), Thread.CurrentThread.ManagedThreadId);
35
36            decimal val = del.EndInvoke(asyncResult);
37
38            Console.WriteLine("[(#{2}){0}]:Output Result:{1}", DateTime.Now.ToString(), val + para, Thread.CurrentThread.ManagedThreadId);
39        }

40    }

41
42      public   class  AsyncTest
43      {
44        public delegate decimal SalaryEventHandler(decimal salary, int monthCount, decimal bonus); // 对应YearlySalary方法
45        public decimal YearlySalary(decimal salary, int monthCount, decimal bonus)
46        {
47            //模拟耗时/复杂的逻辑计算.
48            Thread.Sleep(3000);//等待3秒,注2
49            return salary * monthCount + bonus;
50        }

51    }



1

我们看到DoAntherJob 比异步YearlySalary2,看代码中(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

 1      class  Program
 2      {
 3        static void Main(string[] args)
 4        {
 5            Console.WriteLine("[(#{1}){0}]:Asynchronous Start", DateTime.Now.ToString(), Thread.CurrentThread.ManagedThreadId);
 6
 7            AsyncTest test = new AsyncTest();
 8            MyThirdAsyncCode.AsyncTest.SalaryEventHandler del = test.YearlySalary;
 9            MyThirdAsyncCode.AsyncTest.AsyncEventHandler asy = test.Hello;
10
11            IAsyncResult salayAsyc = del.BeginInvoke(10000015100000, OnSalaryCallback, null);
12            IAsyncResult helloAsyc = asy.BeginInvoke("Hello Andy", OnHelloCallback, null);
13            //把所有异步的句柄保存到WaitHandle 对象中
14            WaitHandle[] handles = { salayAsyc.AsyncWaitHandle, helloAsyc.AsyncWaitHandle };
15            //阻碍当前线程,直到所有异步调用结束.
16            WaitHandle.WaitAll(handles);
17
18            //开始其他工作.
19            DoAntherJob();
20            Console.ReadLine(); // 让黑屏等待,不会直接关闭..
21        }

22        static void DoAntherJob()
23        {
24            Thread.Sleep(1000);//需要1秒才能完成这个工作,注1
25            Console.WriteLine("[(#{1}){0}]:Do Another Job", DateTime.Now.ToString(), Thread.CurrentThread.ManagedThreadId);
26        }

27        static void OnSalaryCallback(IAsyncResult asyncResult)
28        {
29            //通过AsyncDelegate 获取原始的委托对象
30            AsyncResult obj = (AsyncResult)asyncResult;
31            MyThirdAsyncCode.AsyncTest.SalaryEventHandler del =
(MyThirdAsyncCode.AsyncTest.SalaryEventHandler)obj.AsyncDelegate;
32
33            if (asyncResult.IsCompleted)// 判读是否已经调用完成
34                Console.WriteLine("[(#{1}){0}]:Asynchronous Finished.", DateTime.Now.ToString(), Thread.CurrentThread.ManagedThreadId);
35
36            decimal val = del.EndInvoke(asyncResult);
37            Console.WriteLine("[(#{2}){0}]:Output Result:{1}", DateTime.Now.ToString(), val, Thread.CurrentThread.ManagedThreadId);
38        }

39
40        static void OnHelloCallback(IAsyncResult asyncResult)
41        {
42            //通过AsyncDelegate 获取原始的委托对象
43            AsyncResult obj = (AsyncResult)asyncResult;
44            MyThirdAsyncCode.AsyncTest.AsyncEventHandler del =
(MyThirdAsyncCode.AsyncTest.AsyncEventHandler)obj.AsyncDelegate;
45
46            if (asyncResult.IsCompleted)// 判读是否已经调用完成
47                Console.WriteLine("[(#{1}){0}]:Asynchronous Finished.", DateTime.Now.ToString(), Thread.CurrentThread.ManagedThreadId);
48
49            string val = del.EndInvoke(asyncResult);
50            Console.WriteLine("[(#{2}){0}]:Output Result:{1}", DateTime.Now.ToString(), val, Thread.CurrentThread.ManagedThreadId);
51        }

52    }

53
54      public   class  AsyncTest
55      {
56        public delegate decimal SalaryEventHandler(decimal salary, int monthCount, decimal bonus); // 对应YearlySalary方法
57        public delegate string AsyncEventHandler(string name); // 对应Hello 方法
58        public string Hello(string name)
59        {
60            //模拟耗时/复杂的逻辑计算.等待5秒
61            Thread.Sleep(5000);
62            return "Hello:" + name;
63        }

64        public decimal YearlySalary(decimal salary, int monthCount, decimal bonus)
65        {
66            //模拟耗时/复杂的逻辑计算.
67            Thread.Sleep(3000);//等待3秒
68            return salary * monthCount + bonus;
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

1 static   void  OnSalaryCallback(IAsyncResult asyncResult)
2          {
3            //通过AsyncDelegate 获取原始的委托对象
4            AsyncResult obj = (AsyncResult)asyncResult;
5            MyThirdAsyncCode.AsyncTest.SalaryEventHandler del =
(MyThirdAsyncCode.AsyncTest.SalaryEventHandler)obj.AsyncDelegate;
6
7            decimal val = del.EndInvoke(asyncResult);
8            asyncResult.AsyncWaitHandle.Close();//显示的释放资源
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

  1   internal   class  AsyncResultNoResult : IAsyncResult
  2      {
  3        // Fields set at construction which never change while 
  4        // operation is pending
  5        private readonly AsyncCallback m_AsyncCallback;
  6        private readonly Object m_AsyncState;
  7
  8        // Fields set at construction which do change after 
  9        // operation completes
 10        private const Int32 c_StatePending = 0
 11        private const Int32 c_StateCompletedSynchronously = 1;
 12        private const Int32 c_StateCompletedAsynchronously = 2;
 13        private Int32 m_CompletedState = c_StatePending;
 14
 15        // Field that may or may not get set depending on usage
 16        private ManualResetEvent m_AsyncWaitHandle;
 17
 18        // Fields set when operation completes
 19        private Exception m_exception;
 20
 21        public AsyncResultNoResult(AsyncCallback asyncCallback, Object state)
 22        {
 23            m_AsyncCallback = asyncCallback;
 24            m_AsyncState = state;
 25        }

 26
 27        public void SetAsCompleted(
 28           Exception exception, Boolean completedSynchronously)
 29        {
 30            // Passing null for exception means no error occurred. 
 31            // This is the common case
 32            m_exception = exception;
 33
 34            // The m_CompletedState field MUST be set prior calling the callback
 35            Int32 prevState = Interlocked.Exchange(ref m_CompletedState,
 36               completedSynchronously ? c_StateCompletedSynchronously :
 37               c_StateCompletedAsynchronously);
 38            if (prevState != c_StatePending)
 39                throw new InvalidOperationException(
 40                    "You can set a result only once");
 41
 42            // If the event exists, set it
 43            if (m_AsyncWaitHandle != null) m_AsyncWaitHandle.Set();
 44
 45            // If a callback method was set, call it
 46            if (m_AsyncCallback != null) m_AsyncCallback(this);
 47        }

 48
 49        public void EndInvoke()
 50        {
 51            // This method assumes that only 1 thread calls EndInvoke 
 52            // for this object
 53            if (!IsCompleted)
 54            {
 55                // If the operation isn't done, wait for it
 56                AsyncWaitHandle.WaitOne();
 57                AsyncWaitHandle.Close();
 58                m_AsyncWaitHandle = null;  // Allow early GC
 59            }

 60
 61            // Operation is done: if an exception occured, throw it
 62            if (m_exception != nullthrow m_exception;
 63        }

 64
 65        Implementation of IAsyncResult
115    }

 

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

Code3.2

 1 internal   class  AsyncResult < TResult >  : AsyncResultNoResult
 2      {
 3        // Field set when operation completes
 4        private TResult m_result = default(TResult);
 5
 6        public AsyncResult(AsyncCallback asyncCallback, Object state) :
 7            base(asyncCallback, state) { }
 8
 9        public void SetAsCompleted(TResult result,
10           Boolean completedSynchronously)
11        {
12            // Save the asynchronous operation's result
13            m_result = result;
14
15            // Tell the base class that the operation completed 
16            // sucessfully (no exception)
17            base.SetAsCompleted(null, completedSynchronously);
18        }

19
20        new public TResult EndInvoke()
21        {
22            base.EndInvoke(); // Wait until operation has completed 
23            return m_result;  // Return the result (if above didn't throw)
24        }

25    }


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

Code3.3

 1 internal   sealed   class  LongTask
 2      {
 3        private Int32 m_ms;  // Milliseconds;
 4
 5        public LongTask(Int32 seconds)
 6        {
 7            m_ms = seconds * 1000;
 8        }

 9
10        // Synchronous version of time-consuming method
11        public DateTime DoTask()
12        {
13            Thread.Sleep(m_ms);  // Simulate time-consuming task
14            return DateTime.Now; // Indicate when task completed
15        }

16
17        // Asynchronous version of time-consuming method (Begin part)
18        public IAsyncResult BeginDoTask(AsyncCallback callback, Object state)
19        {
20            // Create IAsyncResult object identifying the 
21            // asynchronous operation
22            AsyncResult<DateTime> ar = new AsyncResult<DateTime>(
23               callback, state);
24
25            // Use a thread pool thread to perform the operation
26            ThreadPool.QueueUserWorkItem(DoTaskHelper, ar);
27
28            return ar;  // Return the IAsyncResult to the caller
29        }

30
31        // Asynchronous version of time-consuming method (End part)
32        public DateTime EndDoTask(IAsyncResult asyncResult)
33        {
34            // We know that the IAsyncResult is really an 
35            // AsyncResult<DateTime> object
36            AsyncResult<DateTime> ar = (AsyncResult<DateTime>)asyncResult;
37
38            // Wait for operation to complete, then return result or 
39            // throw exception
40            return ar.EndInvoke();
41        }

42
43        // Asynchronous version of time-consuming method (private part 
44        // to set completion result/exception)
45        private void DoTaskHelper(Object asyncResult)
46        {
47            // We know that it's really an AsyncResult<DateTime> object
48            AsyncResult<DateTime> ar = (AsyncResult<DateTime>)asyncResult;
49            try
50            {
51                // Perform the operation; if sucessful set the result
52                DateTime dt = DoTask();
53                ar.SetAsCompleted(dt, false);
54            }

55            catch (Exception e)
56            {
57                // If operation fails, set the exception
58                ar.SetAsCompleted(e, false);
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

 1static class Program
 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

 1static class Program
 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

 1static class Program
 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

Code
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

 1static class Program
 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

 1        delegate void DoWork();
 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

 1        delegate void DoWork();
 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) (简单的说就是”简化后的委托”)

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值