使用委托进行异步编程
早上在博客园看到一篇关于异步编程的文章,发现里面讲的BeginInvoke和EndInvoke是自己的知识盲点。马上去MSDN上面看了下,总结如下。
对于某些耗时间的方法,我们需要进行异步调用,.NET FrameWork允许我们异步调用任何方法,前提是我们需要为进行异步调用的方法定义一个具有相同签名的委托(即返回值和参数是一致的)。公共语言运行时会自动使用适当的签名(参数)为该委托定义 BeginInvoke 和 EndInvoke 方法。
BeginInvoke 方法启动异步调用(注:这里的异步调用并没有启用新的线程,而是将IRP发送给设备,然后立即返回)。 该方法与您需要异步执行的方法具有相同的参数,还有另外两个可选参数。 第一个参数是一个 AsyncCallback 委托,该委托引用在异步调用完成时要调用的方法。 第二个参数是一个用户定义的对象,该对象将信息传递到回调方法(可以在IAsyncResult的AsyncState里面访问到)。 BeginInvoke 立即返回,不等待异步调用完成。 BeginInvoke 返回一个 IAsyncResult,后者可用于监视异步调用的进度。
EndInvoke 方法检索异步调用的结果。 在调用 BeginInvoke 之后随时可以调用该方法。 如果异步调用尚未完成,则 EndInvoke 会一直阻止调用线程,直到异步调用完成。 EndInvoke 的参数包括您需要异步执行的方法的 out 和 ref 参数,以及由 BeginInvoke 返回的 IAsyncResult。
使用 BeginInvoke 和 EndInvoke 进行异步调用的四种常用方法。 调用 BeginInvoke 之后,您可以执行下列操作:
-
进行某些操作,然后调用 EndInvoke 一直阻止到调用完成。
-
使用 IAsyncResult.AsyncWaitHandle 属性获取 WaitHandle,使用其 WaitOne 方法阻止执行,直至 WaitHandle 收到信号,然后调用 EndInvoke。
-
轮询由 BeginInvoke 返回的 IAsyncResult,以确定异步调用何时完成,然后调用 EndInvoke。
-
将用于回调方法的委托传递给 BeginInvoke。 异步调用完成后,将在 ThreadPool 线程上执行该方法。 回调方法调用 EndInvoke。
将被调用的异步方法:
1 class AsyncCallClass
2 {
3 //为什么类里面的委托是类似于静态的变量,只能通过类名来访问到,通过对象访问不到
4 public delegate string AsyncDelegate(int callDuration, out int threadId);
5
6 public string AsyncMethod(int callDuration, out int threadId)
7 {
8 Console.WriteLine("The AsyncMethod Begin");
9 Thread.Sleep(callDuration);
10 threadId = Thread.CurrentThread.ManagedThreadId;
11 return string.Format("My call time is:{0}",callDuration);
12 }
13 }
代码1:使用EndInvoke方法:
1 //进行异步调用,开始进行调用
2 IAsyncResult asyncResult = asyncDelegate.BeginInvoke(3000, out threadId, null, null);
3 //主线程继续做自己的事情
4 Console.WriteLine(string.Format("The main thread {0} continue do something", Thread.CurrentThread.ManagedThreadId));
5 //获取异步调用的返回值
6 string returnValue = asyncDelegate.EndInvoke(out threadId, asyncResult);
7 Console.WriteLine("The async thread is {0},return value is {1}", threadId, returnValue);
代码2:使用WaitHandle:
1 //使用WaitHandle
2 IAsyncResult asyncResult2 = asyncDelegate.BeginInvoke(5000, out threadId, null, null);
3 Console.WriteLine(string.Format("The main thread {0} continue do something", Thread.CurrentThread.ManagedThreadId));
4 Console.WriteLine("阻塞主线程,等待异步线程完成后再继续");
5 asyncResult2.AsyncWaitHandle.WaitOne();
6 string returnValue2 = asyncDelegate.EndInvoke(out threadId, asyncResult2);
7 Console.WriteLine("The async thread is {0},return value is {1}", threadId, returnValue2);
代码3:使用轮询机制:
1 //使用轮询机制
2 IAsyncResult asyncResult3 = asyncDelegate.BeginInvoke(3000,out threadId,null,null);
3 Console.WriteLine(string.Format("The main thread {0} continue do something", Thread.CurrentThread.ManagedThreadId));
4 while (!asyncResult3.IsCompleted)
5 {
6 Thread.Sleep(250);
7 Console.WriteLine("查询异步线程是否完成");
8 }
9 string returnValue3 = asyncDelegate.EndInvoke(out threadId, asyncResult3);
10 Console.WriteLine("The async thread is {0},return value is {1}", threadId, returnValue3);
代码4:使用委托回调:
1 asyncDelegate.BeginInvoke(3000,out threadId,async.CallMethod,"The async thread is {0},return value is {1}");
2 Console.WriteLine("主线程继续执行");
3 Thread.Sleep(4000);
4 Console.WriteLine("主线程结束");
回调函数:
1 public void CallMethod(IAsyncResult ar)
2 {
3 int threadId;
4 //需要进行转换,以获取调用BeginInvoke的委托
5 AsyncResult result = (AsyncResult)ar;
6 AsyncDelegate asyncDelegate =(AsyncDelegate)result.AsyncDelegate;
7 string formatString = result.AsyncState.ToString();
8 //调用委托的EndInvoke终止异步线程
9 string returnValue=asyncDelegate.EndInvoke(out threadId, ar);
10 Console.WriteLine(formatString, threadId, returnValue);
11
12 }
备注:
如果启动异步调用的线程不需要是处理结果的线程,则可以在调用完成时执行回调方法。回调方法在 ThreadPool 线程上执行。 MSDN上面的这句话是怎么理解更好。其英文原版:
If the thread that initiates the asynchronous call does not need to be the thread that processes the results, you can execute a callback method when the call completes. The callback method is executed on a ThreadPool thread.
应该是如果初始化异步线程的主线程不需要去处理异步线程的结果时,我们可以采用回调的方法,疑问:是什么原因造成主线程无需去处理异步线程返回的结果呢?
答案: 当我们使用进行一次web请求的时候,会启动一个应答此request请求的线程,如果此线程需要读取数据库,则会进行I/O操作,如果这里并没有使用异步去实现。则在进行I/O操作的时候这个线程并没有做其他的事情。这个线程会被阻塞,当再次有请求到来的时候又要重新去创建一个新的线程。这样当请求多的时候会创建很多线程,在结束I/O操作之后,等待I/O请求的线程就会被重新唤醒。这样就需要进行线程的切换。由于创建线程,切换线程都会有性能上的丢失,因此这种方法是不可取的。在使用异步调用的情况下,在等待I/O操作的时候,这个接受web请求的线程是不会被阻塞的,系统会将其放回线程池,由于利用异步指定委托回调,当I/O完成操作的时候,会自动在线程池中取出一个线程执行接下来的操作。这样之前的线程就可以去接受其他的request请求。
这将貌似挺合理的!但是在整个asp.net请求过程中,在处理管道里面发生哪些还有待继续探讨!