1.为什么要使用异步编程?
普通程序单线程运行,如果遇到I/O操作,大量数据库操作,使用普通方法会使得当前线程阻塞。使用异步编程就会另外开启一个线程并行 ,解决了这些问题。
2. 关键点
在C#中,用户启动的线程资源是必须收回的,因为浪费可耻。启动线程容易,但是何时收回,如何收回就比较值得考虑了,根据收回的方式,可以将异步调用,分为两种方式,一种是主动收回(通过主线程阻塞,等待异步线程完成,然后在主线程收回线程资源),被动收回(当异步线程运行完后,触发回调函数,在回调函数中收回线程资源)。
异步过程:
(1)准备一个准备进行异步调用的方法,如TestMethod
class AsyncDemo
{
public string TestMethod(int callDuration,out int threadId)
{
Console.WriteLine("Test method begins.");
Thread.Sleep(callDuration);
threadId = AppDomain.GetCurrentThreadId();
return "MyCallTime was " + callDuration.ToString();
}
}
(2)声明和这个方法对应的委托
public delegate string AsynDelegate(int callDuration,out int threadId);
(3)声明委托变量,并且将(1)中的方法绑定到这个变量上
int threadId;
AsyncDemo ad = new AsyncDemo();
AsynDelegate asynDelegate = new AsynDelegate(ad.TestMethod);
(4)进行异步调用
IAsyncResult ir = asynDelegate.BeginInvoke(3000, out threadId, null, null);
(5)调用结束后收回线程资源
string ret = asynDelegate.EndInvoke(out threadId, ir);
3. 如何收回线程资源
阻塞主线程,有三种方式
- 使用EndInvoke直到结束
- 使用waithandle等待调用结束
- 轮询异步调用完成
(1)使用EndInvoke直到结束
IAsyncResult ir = asynDelegate.BeginInvoke(3000, out threadId, null, null);
string ret = asynDelegate.EndInvoke(out threadId, ir);//通过out可以将异步调用中的变量传递出来,为什么需要ir?程序需要通过ir获得异步委托,从而进行线程资源的收回
BeginInvoke前两个参数是声明TestMethod函数的时候的参数,第三个是回调函数委托,第四个,当使用回调函数时需要将asynDelegate作为参数传入,从而使得在异步结果(ir)中能够拿到asynDelegate变量,从而进行相关操作。
EndInvoke 前面是testMethod中ref和out参数,后面是异步状态变量IAsyncResult
(2)使用waithandle等待调用结束
IAsyncResult ar = asynDelegate.BeginInvoke(3000, out threadId, null, null);
// C#异步调用完成时会发出 WaitHandle 信号 ,程序在此阻塞
ar.AsyncWaitHandle.WaitOne();
string ret = asynDelegate.EndInvoke(out threadId, ar);
(3)轮询异步调用完成
IAsyncResult ar = asynDelegate.BeginInvoke(3000,out threadId, null, null);
//轮询ar.IsCompleted的状态
while(ar.IsCompleted == false) {
Thread.Sleep(10);
}
//会导致程序在此阻塞
string ret = asynDelegate.EndInvoke(out threadId, ar);
等待异步调用结束,使用回调函数收回线程资源
首先声明回调函数:
static void CallbackMethod(IAsyncResult ar) {
int threadId;
// 通过异步结果(IAsyncResult)重新得到委托对象
AsynDelegate asynDelegate = (AsynDelegate)ar.AsyncState;
//在回调函数中调用EndInvoke,收回异步线程的资源
string ret = asynDelegate.EndInvoke(out threadId, ar);
Console.WriteLine("The call executed on thread {0}, with return value \"{1}\".", threadId, ret);
}
然后将回调函数作为异步调用的参数,进行异步调用
IAsyncResult ar = asynDelegate.BeginInvoke(3000, out threadId
, new AsyncCallback(CallbackMethod), asynDelegate);
4. 异步操作与多线程
当需要执行I/O操作时,使用异步操作比使用线程+同步 I/O操作更合适。I/O操作不仅包括了直接的文件、网络的读写,还包括数据库操作、Web Service、HttpRequest以及.net Remoting等跨进程的调用。
而线程的适用范围则是那种需要长时间CPU运算的场合,例如耗时较长的图形处理和算法执行。但是往 往由于使用线程编程的简单和符合习惯,所以很多朋友往往会使用线程来执行耗时较长的I/O操作。这样在只有少数几个并发操作的时候还无伤大雅,如果需要处 理大量的并发操作时就不合适了。
而线程的适用范围则是那种需要长时间CPU运算的场合,例如耗时较长的图形处理和算法执行。但是往 往由于使用线程编程的简单和符合习惯,所以很多朋友往往会使用线程来执行耗时较长的I/O操作。这样在只有少数几个并发操作的时候还无伤大雅,如果需要处 理大量的并发操作时就不合适了。
5. 以下为程序代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace AsynTest
{
class AsyncDemo
{
public string TestMethod(int callDuration,out int threadId)
{
Console.WriteLine("Test method begins.");
Thread.Sleep(callDuration);
threadId = AppDomain.GetCurrentThreadId();
return "MyCallTime was " + callDuration.ToString();
}
}
public delegate string AsynDelegate(int callDuration,out int threadId);
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace AsynTest
{
class Program
{
static void Main(string[] args)
{
int threadId;
AsyncDemo ad = new AsyncDemo();
AsynDelegate asynDelegate = new AsynDelegate(ad.TestMethod);
//1. 使用EndInvoke直到结束
/* IAsyncResult ir = asynDelegate.BeginInvoke(3000, out threadId, null, null);
//此时异步线程还没启动,启动异步线程需要一定开销
Console.WriteLine("Main thread {0} does some work.", AppDomain.GetCurrentThreadId());
string ret = asynDelegate.EndInvoke(out threadId, ir);//通过out可以将异步调用中的变量传递出来,为什么需要ir
Console.WriteLine("The call executed on thread {0},with return value \"{1}\".", threadId, ret);
Console.ReadLine();
*/
//2.使用waithandle等待调用结束
/* IAsyncResult ar = asynDelegate.BeginInvoke(3000, out threadId, null, null);
Console.WriteLine("Main thread {0} does some work.", AppDomain.GetCurrentThreadId());
// C#异步调用完成时会发出 WaitHandle 信号 ,程序在此阻塞
ar.AsyncWaitHandle.WaitOne();
string ret = asynDelegate.EndInvoke(out threadId, ar);
Console.WriteLine("The call executed on thread {0}, with return value \"{1}\".", threadId, ret);
Console.ReadLine();
*/
//3.轮询异步调用完成
/* IAsyncResult ar = asynDelegate.BeginInvoke(3000,out threadId, null, null);
Console.WriteLine("Main thread {0} does some work.", AppDomain.GetCurrentThreadId());
//轮询ar.IsCompleted的状态
while(ar.IsCompleted == false) {
Thread.Sleep(10);
}
// Call EndInvoke to retrieve the results.
string ret = asynDelegate.EndInvoke(out threadId, ar);
Console.WriteLine("The call executed on thread {0}, with return value \"{1}\".", threadId, ret);
Console.ReadLine();
*/
//4.调用完成时执行回调方法
IAsyncResult ar = asynDelegate.BeginInvoke(3000, out threadId, new AsyncCallback(CallbackMethod), asynDelegate);
Console.WriteLine("Press Enter to close application.");
Console.ReadLine();
}
// 这个回调函数的签名(参数列表+返回值)必须和内置回调委托一致,这样才能作为参数传递
// AsyncCallback delegate.
static void CallbackMethod(IAsyncResult ar) {
int threadId;
// 通过异步结果(IAsyncResult)重新得到委托对象
AsynDelegate asynDelegate = (AsynDelegate)ar.AsyncState;
//在回调函数中调用EndInvoke,收回异步线程的资源
string ret = asynDelegate.EndInvoke(out threadId, ar);
Console.WriteLine("The call executed on thread {0}, with return value \"{1}\".", threadId, ret);
}
}
}