C#异步方法调用(四大方法详解)
计算机中有些处理比较耗时。调用这种处理代码时,调用方如果站在那里苦苦等待,会严重影响程序性能。例如,某个程序启动后如果需要打开文件读出其中的数据,再根据这些数据进行一系列初始化处理,程序主窗口将迟迟不能显示,让用户感到这个程序怎么等半天也不出来,太差劲了。借助异步调用可以把问题轻松化解:把整个初始化处理放进一个单独线程,主线程启动此线程后接着往下走,让主窗口瞬间显示出来。等用户盯着窗口犯呆时,初始化处理就在背后悄悄完成了。程序开始稳定运行以后,还可以继续使用这种技巧改善人机交互的瞬时反应。用户点击鼠标时,所激发的操作如果较费时,再点击鼠标将不会立即反应,整个程序显得很沉重。借助异步调用处理费时的操作,让主线程随时恭候下一条消息,用户点击鼠标时感到轻松快捷,肯定会对软件产生好感。
异步调用用来处理从外部输入的数据特别有效。假如计算机需要从一台低速设备索取数据,然后是一段冗长的数据处理过程,采用同步调用显然很不合算:计算机先向外部设备发出请求,然后等待数据输入;而外部设备向计算机发送数据后,也要等待计算机完成数据处理后再发出下一条数据请求。双方都有一段等待期,拉长了整个处理过程。其实,计算机可以在处理数据之前先发出下一条数据请求,然后立即去处理数据。如果数据处理比数据采集快,要等待的只有计算机,外部设备可以连续不停地采集数据。如果计算机同时连接多台输入设备,可以轮流向各台设备发出数据请求,并随时处理每台设备发来的数据,整个系统可以保持连续高速运转。编程的关键是把数据索取代码和数据处理代码分别归属两个不同的线程。数据处理代码调用一个数据请求异步函数,然后径自处理手头的数据。待下一组数据到来后,数据处理线程将收到通知,结束 wait 状态,发出下一条数据请求,然后继续处理数据。
异步调用时,调用方不等被调方返回结果就转身离去,因此必须有一种机制让被调方有了结果时能通知调用方。在同一进程中有很多手段可以利用,笔者常用的手段是回调、event 对象和消息。
回调方式很简单:调用异步函数时在参数中放入一个函数地址,异步函数保存此地址,待有了结果后回调此函数便可以向调用方发出通知。如果把异步函数包装进一个对象中,可以用事件取代回调函数地址,通过事件处理例程向调用方发通知。
event 是 windows 系统提供的一个常用同步对象,以在异步处理中对齐不同线程之间的步点。如果调用方暂时无事可做,可以调用 wait 函数等在那里,此时 event 处于 nonsignaled 状态。当被调方出来结果之后,把 event 对象置于 signaled 状态,wait 函数便自动结束等待,使调用方重新动作起来,从被调方取出处理结果。这种方式比回调方式要复杂一些,速度也相对较慢,但有很大的灵活性,可以搞出很多花样以适应比较复杂的处理系统。
借助 windows 消息发通知是个不错的选择,既简单又安全。程序中定义一个用户消息,并由调用方准备好消息处理例程。被调方出来结果之后立即向调用方发送此消息,并通过 wparam 和 lparam 这两个参数传送结果。消息总是与窗口 handle 关联,因此调用方必须借助一个窗口才能接收消息,这是其不方便之处。另外,通过消息联络会影响速度,需要高速处理时回调方式更有优势。
如果调用方和被调方分属两个不同的进程,由于内存空间的隔阂,一般是采用 windows 消息发通知比较简单可靠,被调方可以借助消息本身向调用方传送数据。event 对象也可以通过名称在不同进程间共享,但只能发通知,本身无法传送数据,需要借助 windows 消息和 filemapping 等内存共享手段或借助 mailslot 和 pipe 等通信手段。
异步调用原理并不复杂,但实际使用时容易出莫名其妙的问题,特别是不同线程共享代码或共享数据时容易出问题,编程时需要时时注意是否存在这样的共享,并通过各种状态标志避免冲突。windows 系统提供的 mutex 对象用在这里特别方便。mutex 同一时刻只能有一个管辖者。一个线程放弃管辖权后,另一线程才能接管。当某线程执行到敏感区之前先接管 mutex,使其他线程被 wait 函数堵在身后;脱离敏感区之后立即放弃管辖权,使 wait 函数结束等待,另一个线程便有机会光临此敏感区。这样就可以有效避免多个线程进入同一敏感区。
由于异步调用容易出问题,要设计一个安全高效的编程方案需要比较多的设计经验,所以最好不要滥用异步调用。同步调用毕竟让人更舒服些:不管程序走到哪里,只要死盯着移动点就能心中有数,不至于象异步调用那样,总有一种四面受敌、惶惶不安的感觉。必要时甚至可以把异步函数转换为同步函数。方法很简单:调用异步函数后马上调用 wait 函数等在那里,待异步函数返回结果后再继续往下走。
C#提供了异步方法调用的功能,先创建一个委托,该委托的签名要与需要异步执行的方法定义相匹配。还是以代码来说明。
委托的声明:
public delegate string AsyncDelegateGetPage( Uri uri , out string url );
调用方代码:
AsyncCallback callback = new AsyncCallback( ProcessPage ); //回调函数声明
AsyncDelegateGetPage ad = new AsyncDelegateGetPage( GetPageSource );//实例化委托类型
IAsyncResult ar = ad.BeginInvoke( this.ObtainWork() ,out url, callback , ad );//开始调用
ar.AsyncWaitHandle.WaitOne();//阻塞主线程,直到异步完成,用于多线程同步,一般可以不需要。
BeginInvoke方法为开始异步调用,其参数为动态的,依据委托的签名。上面的情况,参数为:Uri , out url,回调函数实例(可为null),委托实例(可为null)。即前面的几个参数为委托方法的参数,后面2个分别是回调函数实例和委托实例。参数委托实例用于将该实例传递到回调函数中。注意回调函数的必须为void ,并且参数为IAsyncResult 类型。
委托的方法代码:
public String GetPageSource( Uri uri , out string url ){
url = uri.ToString();
return this.GetPageSource( uri );
}
回调函数代码:
void ProcessPage( IAsyncResult ar ){
AsyncDelegateGetPage andl = (AsyncDelegateGetPage)ar.AsyncState;
string url;
string source = andl.EndInvoke( out url, ar );
}
委托类型的EndInvoke方法,参数为异步结果,IAsyncResult类型。执行该方法后,返回异步调用的结果。
以上是针对有回调函数的情况。
附:
C#异步调用四大方法是什么呢?C#异步调用四大方法的使用是如何进行的呢?让我们首先了解下什么时候用到C#异步调用:
.NET Framework 允许您C#异步调用任何方法。定义与您需要调用的方法具有相同签名的委托;公共语言运行库将自动为该委托定义具有适当签名的 BeginInvoke 和 EndInvoke 方法。
BeginInvoke 方法用于启动C#异步调用。它与您需要异步执行的方法具有相同的参数,只不过还有两个额外的参数(将在稍后描述)。BeginInvoke 立即返回,不等待C#异步调用完成。BeginInvoke 返回 IasyncResult,可用于监视调用进度。
EndInvoke 方法用于检索C#异步调用结果。调用 BeginInvoke 后可随时调用 EndInvoke 方法;如果C#异步调用未完成,EndInvoke 将一直阻塞到C#异步调用完成。EndInvoke 的参数包括您需要异步执行的方法的 out 和 ref 参数(在 Visual Basic 中为
注意 Visual Studio .NET 中的智能感知功能会显示 BeginInvoke 和 EndInvoke 的参数。如果您没有使用 Visual Studio 或类似的工具,或者您使用的是 C# 和 Visual Studio .NET,请参见异步方法签名获取有关运行库为这些方法定义的参数的描述。
本主题中的代码演示了四种使用 BeginInvoke 和 EndInvoke 进行C#异步调用的常用方法。调用了 BeginInvoke 后,可以:
· 进行某些操作,然后调用 EndInvoke 一直阻塞到调用完成。
· 使用 IAsyncResult.AsyncWaitHandle 获取 WaitHandle,使用它的 WaitOne 方法将执行一直阻塞到发出 WaitHandle 信号,然后调用 EndInvoke。
· 轮询由 BeginInvoke 返回的 IAsyncResult,确定C#异步调用何时完成,然后调用 EndInvoke。
· 将用于回调方法的委托传递给 BeginInvoke。该方法在C#异步调用完成后在 ThreadPool 线程上执行,它可以调用 EndInvoke。
警告:始终在C#异步调用完成后调用 EndInvoke。
测试方法和异步委托
四个示例全部使用同一个长期运行的测试方法 TestMethod。该方法显示一个表明它已开始处理的控制台信息,休眠几秒钟,然后结束。TestMethod 有一个 out 参数(在 Visual Basic 中为
下面的代码示例显示 TestMethod 以及代表 TestMethod 的委托;若要使用任一示例,请将示例代码追加到这段代码中。
注意 为了简化这些示例,TestMethod 在独立于 Main() 的类中声明。或者,TestMethod 可以是包含 Main() 的同一类中的 static 方法(在 Visual Basic 中为 Shared)。
- using System;
- using System.Threading;
- public class AsyncDemo {
- // The method to be executed asynchronously.
- //
- public string TestMethod(
- int callDuration, out int threadId) {
- Console.WriteLine("Test method begins.");
- Thread.Sleep(callDuration);
- threadId = AppDomain.GetCurrentThreadId();
- return "MyCallTime was " + callDuration.ToString();
- }
- }
- // The delegate must have the same signature as the method
- // you want to call asynchronously.
- public delegate string AsyncDelegate(
- int callDuration, out int threadId);
- using System;
- using System.Threading;
- public class AsyncDemo {
- // The method to be executed asynchronously.
- //
- public string TestMethod(
- int callDuration, out int threadId) {
- Console.WriteLine("Test method begins.");
- Thread.Sleep(callDuration);
- threadId = AppDomain.GetCurrentThreadId();
- return "MyCallTime was " + callDuration.ToString();
- }
- }
- // The delegate must have the same signature as the method
- // you want to call asynchronously.
- public delegate string AsyncDelegate(
- int callDuration, out int threadId);
C#异步调用四大方法之使用 EndInvoke 等待异步调用
异步执行方法的最简单方式是以 BeginInvoke 开始,对主线程执行一些操作,然后调用 EndInvoke。EndInvoke 直到C#异步调用完成后才返回。这种技术非常适合文件或网络操作,但是由于它阻塞 EndInvoke,所以不要从用户界面的服务线程中使用它。
- public class AsyncMain {
- static void Main(string[] args) {
- // The asynchronous method puts the thread id here.
- int threadId;
- // Create an instance of the test class.
- AsyncDemo ad = new AsyncDemo();
- // Create the delegate.
- AsyncDelegate dlgt = new AsyncDelegate(ad.TestMethod);
- // Initiate the asychronous call.
- IAsyncResult ar = dlgt.BeginInvoke(3000,
- out threadId, null, null);
- Thread.Sleep(0);
- Console.WriteLine("Main thread {0} does some work.",
- AppDomain.GetCurrentThreadId());
- // Call EndInvoke to Wait for
- //the asynchronous call to complete,
- // and to retrieve the results.
- string ret = dlgt.EndInvoke(out threadId, ar);
- Console.WriteLine("The call executed on thread {0},
- with return value \"{1}\".", threadId, ret);
- }
- }
C#异步调用四大方法之使用 WaitHandle 等待异步调用
等待 WaitHandle 是一项常用的线程同步技术。您可以使用由 BeginInvoke 返回的 IAsyncResult 的 AsyncWaitHandle 属性来获取 WaitHandle。C#异步调用完成时会发出 WaitHandle 信号,而您可以通过调用它的 WaitOne 等待它。
如果您使用 WaitHandle,则在C#异步调用完成之后,但在通过调用 EndInvoke 检索结果之前,可以执行其他处理。
- public class AsyncMain {
- static void Main(string[] args) {
- // The asynchronous method puts the thread id here.
- int threadId;
- // Create an instance of the test class.
- AsyncDemo ad = new AsyncDemo();
- // Create the delegate.
- AsyncDelegate dlgt = new AsyncDelegate(ad.TestMethod);
- // Initiate the asychronous call.
- IAsyncResult ar = dlgt.BeginInvoke(3000,
- out threadId, null, null);
- Thread.Sleep(0);
- Console.WriteLine("Main thread {0} does some work.",
- AppDomain.GetCurrentThreadId());
- // Wait for the WaitHandle to become signaled.
- ar.AsyncWaitHandle.WaitOne();
- // Perform additional processing here.
- // Call EndInvoke to retrieve the results.
- string ret = dlgt.EndInvoke(out threadId, ar);
- Console.WriteLine("The call executed on thread {0},
- with return value \"{1}\".", threadId, ret);
- }
- }
C#异步调用四大方法之轮询异步调用完成
您可以使用由 BeginInvoke 返回的 IAsyncResult 的 IsCompleted 属性来发现C#异步调用何时完成。从用户界面的服务线程中进行C#异步调用时可以执行此操作。轮询完成允许用户界面线程继续处理用户输入。
- public class AsyncMain {
- static void Main(string[] args) {
- // The asynchronous method puts the thread id here.
- int threadId;
- // Create an instance of the test class.
- AsyncDemo ad = new AsyncDemo();
- // Create the delegate.
- AsyncDelegate dlgt = new AsyncDelegate(ad.TestMethod);
- // Initiate the asychronous call.
- IAsyncResult ar = dlgt.BeginInvoke(3000,
- out threadId, null, null);
- // Poll while simulating work.
- while(ar.IsCompleted == false) {
- Thread.Sleep(10);
- }
- // Call EndInvoke to retrieve the results.
- string ret = dlgt.EndInvoke(out threadId, ar);
- Console.WriteLine("The call executed on thread {0},
- with return value \"{1}\".", threadId, ret);
- }
- }
C#异步调用四大方法之异步调用完成时执行回调方法
如果启动异步调用的线程不需要处理调用结果,则可以在调用完成时执行回调方法。回调方法在 ThreadPool 线程上执行。
要使用回调方法,必须将代表该方法的 AsyncCallback 委托传递给 BeginInvoke。也可以传递包含回调方法将要使用的信息的对象。例如,可以传递启动调用时曾使用的委托,以便回调方法能够调用 EndInvoke。
- public class AsyncMain {
- // Asynchronous method puts the thread id here.
- private static int threadId;
- static void Main(string[] args) {
- // Create an instance of the test class.
- AsyncDemo ad = new AsyncDemo();
- // Create the delegate.
- AsyncDelegate dlgt = new AsyncDelegate(ad.TestMethod);
- // Initiate the asychronous call. Include an AsyncCallback
- // delegate representing the callback method, and the data
- // needed to call EndInvoke.
- IAsyncResult ar = dlgt.BeginInvoke(3000,
- out threadId,
- new AsyncCallback(CallbackMethod),
- dlgt );
- Console.WriteLine("Press Enter to close application.");
- Console.ReadLine();
- }
- // Callback method must have the same signature as the
- // AsyncCallback delegate.
- static void CallbackMethod(IAsyncResult ar) {
- // Retrieve the delegate.
- AsyncDelegate dlgt = (AsyncDelegate) ar.AsyncState;
- // Call EndInvoke to retrieve the results.
- string ret = dlgt.EndInvoke(out threadId, ar);
- Console.WriteLine("The call executed on thread {0},
- with return value \"{1}\".", threadId, ret);
- }
- }
C#异步调用四大方法的基本内容就向你介绍到这里,希望对你了解和学习C#异步调用有所帮助。