C#关于异步调用的方法

来源:互联网 发布:上班族网络兼职 编辑:程序博客网 时间:2024/04/30 05:19

C# 委托的三种调用示例(同步调用 异步调用 异步回调)

作者: 字体:[增加 减小] 类型:转载 时间:2013-12-04 我要评论

本文将主要通过同步调用、异步调用、异步回调三个示例来讲解在用委托执行同一个加法类的时候的的区别和利弊

首先,通过代码定义一个委托和下面三个示例将要调用的方法:

复制代码 代码如下:

public delegate int AddHandler(int a,int b);
    public class 加法类
    {
        public static int Add(int a, int b)
        {
            Console.WriteLine("开始计算:" + a + "+" + b);
            Thread.Sleep(3000); //模拟该方法运行三秒
            Console.WriteLine("计算完成!");
            return a + b;
        }
    }

同步调用

委托的Invoke方法用来进行同步调用。同步调用也可以叫阻塞调用,它将阻塞当前线程,然后执行调用,调用完毕后再继续向下进行。

复制代码 代码如下:

public class 同步调用
{
        static void Main()
        {
            Console.WriteLine("===== 同步调用 SyncInvokeTest =====");
            AddHandler handler = new AddHandler(加法类.Add);
            int result = handler.Invoke(1, 2);
            Console.WriteLine("继续做别的事情。。。");
            Console.WriteLine(result);
            Console.ReadKey();
        }      
}

同步调用会阻塞线程,如果是要调用一项繁重的工作(如大量IO操作),可能会让程序停顿很长时间,造成糟糕的用户体验,这时候异步调用就很有必要了。

异步调用

异步调用不阻塞线程,而是把调用塞到线程池中,程序主线程或UI线程可以继续执行。委托的异步调用通过BeginInvoke和EndInvoke来实现

复制代码 代码如下:

public class 异步调用
{
        static void Main()
        {
            Console.WriteLine("===== 异步调用 AsyncInvokeTest =====");
            AddHandler handler = new AddHandler(加法类.Add);
            //IAsyncResult: 异步操作接口(interface)
            //BeginInvoke: 委托(delegate)的一个异步方法的开始
            IAsyncResult result = handler.BeginInvoke(1, 2, null, null);
            Console.WriteLine("继续做别的事情。。。");
            //异步操作返回
            Console.WriteLine(handler.EndInvoke(result));
            Console.ReadKey();
        }
}

可以看到,主线程并没有等待,而是直接向下运行了。但是问题依然存在,当主线程运行到EndInvoke时,如果这时调用没有结束(这种情况很可能出现),这时为了等待调用结果,线程依旧会被阻塞。

异步委托,也可以参考如下写法:

Action<object> action=(obj)=>method(obj);

action.BeginInvoke(obj,ar=>action.EndInvoke(ar),null);

简简单单两句话就可以完成一部操作。

异步回调

用回调函数,当调用结束时会自动调用回调函数,解决了为等待调用结果,而让线程依旧被阻塞的局面。

复制代码 代码如下:

public class 异步回调
{
        static void Main()
        {
            Console.WriteLine("===== 异步回调 AsyncInvokeTest =====");
            AddHandler handler = new AddHandler(加法类.Add);
            //异步操作接口(注意BeginInvoke方法的不同!)
            IAsyncResult result = handler.BeginInvoke(1,2,new AsyncCallback(回调函数),"AsycState:OK");
            Console.WriteLine("继续做别的事情。。。");
            Console.ReadKey();
        }

        static void 回调函数(IAsyncResult result)
        {     
             //result 是“加法类.Add()方法”的返回值
            //AsyncResult 是IAsyncResult接口的一个实现类,空间:System.Runtime.Remoting.Messaging
            //AsyncDelegate 属性可以强制转换为用户定义的委托的实际类。
            AddHandler handler = (AddHandler)((AsyncResult)result).AsyncDelegate;
            Console.WriteLine(handler.EndInvoke(result));
            Console.WriteLine(result.AsyncState);
        }
}

我定义的委托的类型为AddHandler,则为了访问 AddHandler.EndInvoke,必须将异步委托强制转换为 AddHandler。可以在异步回调函数(类型为 AsyncCallback)中调用 MAddHandler.EndInvoke,以获取最初提交的 AddHandler.BeginInvoke 的结果。

问题:

(1)int result = handler.Invoke(1,2);

为什么Invoke的参数和返回值和AddHandler委托是一样的呢?

答:Invoke方法的参数很简单,一个委托,一个参数表(可选),而Invoke方法的主要功能就是帮助你在UI线程上调用委托所指定的方法。Invoke方法首先检查发出调用的线程(即当前线程)是不是UI线程,如果是,直接执行委托指向的方法,如果不是,它将切换到UI线程,然后执行委托指向的方法。不管当前线程是不是UI线程,Invoke都阻塞直到委托指向的方法执行完毕,然后切换回发出调用的线程(如果需要的话),返回。

所以Invoke方法的参数和返回值和调用他的委托应该是一致的。

(2)IAsyncResult result = handler.BeginInvoke(1,2,null,null);

BeginInvoke : 开始一个异步的请求,调用线程池中一个线程来执行,

返回IAsyncResult 对象(异步的核心). IAsyncResult 简单的说,他存储异步操作的状态信息的一个接口,也可以用他来结束当前异步

注意: BeginInvoke和EndInvoke必须成对调用.即使不需要返回值,但EndInvoke还是必须调用,否则可能会造成内存泄漏。

(3)IAsyncResult.AsyncState 属性:

获取用户定义的对象,它限定或包含关于异步操作的信息。 例如:

复制代码 代码如下:

static void AddComplete(IAsyncResult result)
{  
      AddHandler handler = (AddHandler)result.AsyncState;   
      Console.WriteLine(handler.EndInvoke(result));
}

完整代码如下:

复制代码 代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Runtime.Remoting.Messaging;
namespace ConsoleTest
{
    public delegate int AddHandler(int a,int b);
    public class 加法类
    {
        public static int Add(int a, int b)
        {
            Console.WriteLine("开始计算:" + a + "+" + b);
            Thread.Sleep(3000); //模拟该方法运行三秒
            Console.WriteLine("计算完成!");
            return a + b;
       }
    }

    public class 同步调用
    {
       static void Main()
        {
            Console.WriteLine("===== 同步调用 SyncInvokeTest =====");
            AddHandler handler = new AddHandler(加法类.Add);
            int result = handler.Invoke(1, 2);

            Console.WriteLine("继续做别的事情。。。");
            Console.WriteLine(result);
            Console.ReadKey();
        }
    }

    public class 异步调用
    {
        static void Main()
        {
            Console.WriteLine("===== 异步调用 AsyncInvokeTest =====");
            AddHandler handler = new AddHandler(加法类.Add);
            //IAsyncResult: 异步操作接口(interface)
            //BeginInvoke: 委托(delegate)的一个异步方法的开始
            IAsyncResult result = handler.BeginInvoke(1, 2, null, null);
            Console.WriteLine("继续做别的事情。。。");
            //异步操作返回
            Console.WriteLine(handler.EndInvoke(result));
            Console.ReadKey();
        }
    }

    public class 异步回调
    {
        static void Main()
        {
            Console.WriteLine("===== 异步回调 AsyncInvokeTest =====");
            AddHandler handler = new AddHandler(加法类.Add);
            //异步操作接口(注意BeginInvoke方法的不同!)
            IAsyncResult result = handler.BeginInvoke(1,2,new AsyncCallback(回调函数),"AsycState:OK");
            Console.WriteLine("继续做别的事情。。。");
            Console.ReadKey();
        }

        static void 回调函数(IAsyncResult result)
        {     
             //result 是“加法类.Add()方法”的返回值
            //AsyncResult 是IAsyncResult接口的一个实现类,引用空间:System.Runtime.Remoting.Messaging
            //AsyncDelegate 属性可以强制转换为用户定义的委托的实际类。
            AddHandler handler = (AddHandler)((AsyncResult)result).AsyncDelegate;
            Console.WriteLine(handler.EndInvoke(result));
            Console.WriteLine(result.AsyncState);
        }
    }
}

最近的项目需要涉及到数据库的读写,在一个接口里面调用其他的接口。一般来说,频繁的数据库读写会有性能和时间上的问题。我遇到的情况就是:接口A调用数据库,平均耗时4s,接口B调用数据库,平均耗时5s,接口C调用接口A和B,这样它们总的耗时接近10s,对于http请求来说,这个时间无法忍受!(A,B的接口耗时那么久,也需要解决,下次再写)。 

解决办法有两个:1.缩短接口A和B的耗时;2.调用接口A和B做成异步方式。我采用的是后者

参考文章 :1.http://blog.163.com/lixueyu1004@126/blog/static/50282494201163111310505/;2.http://wenku.baidu.com/view/282cf86d25c52cc58bd6be53.html

仿照它,我重写了调用接口A和B的方法

[csharp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public delegate string DelegateWithParameters(int param1, string param2);  
  2.   private static void AsynCallUserReportDiagnosis(int reportId,string urlAddr)  
  3.   {  
  4.       // 创建包含CallUserReportDiagnosis函数的委托对象  
  5.       DelegateWithParameters invoker = new DelegateWithParameters(CallUserReportDiagnosis);  
  6.       invoker.BeginInvoke(reportId,urlAddr, nullnull);  
  7.   }  
  8.   private static void AsynCallUserReportMark(int reportId,string urlAddr)  
  9.   {  
  10.       // 创建包含CallUserReportMark函数的委托对象  
  11.       DelegateWithParameters invoker = new DelegateWithParameters(CallUserReportMark);  
  12.       invoker.BeginInvoke(reportId,urlAddr ,nullnull);  
  13.   }  

其中CallUserReportDiagnosis()就是接口A执行的函数,CallUserReportMark是接口B的

如何在调用CallUserReportDiagnosis()方法的同时,使主线程可以不必等待CallUserReportDiagnosis()的完成,一直能够得到相应呢?这很重要,它意味着在函数执行的同时,主线程依然是非阻塞状态。在后台服务类型的程序中,非阻塞的状态意味着该应用服务可以在等待一项任务的同时去接受另一项任务;在传统的单线程程序中,意味着主线程(即winform的UI线程)依然可以对用户的操作得到响应,避免了”假死”。我们继续调用CallUserReportDiagnosis()函数,但这次要引入BeginInvoke。

[csharp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. DelegateWithParameters invoker = new DelegateWithParameters(CallUserReportDiagnosis);  
  2.         invoker.BeginInvoke(reportId,urlAddr, nullnull);  

注意BeginInvoke这行代码,它会执行委托所调用的函数体。同时,调用BeginInvoke方法的线程(以下简称为调用线程)会立即得到响应,而不必等待CallUserReportDiagnosis()函数 的完成。以上代码是异步的,调用线程完全可以在调用函数的同时处理其他工作,但是不足的是我们仍然不知道对于CallUserReportDiagnosis()函数的调用何时会结束。Delegate.BeginInvoke是异步方式的。如果你要执行一项任务,但并不关心它何时完成,我们就可以使用BeginInvoke,它不会带来调用线程的阻塞。

一旦你使用.NET完成了一次异步调用,它都需要一个线程来处理异步工作内容(以下简称异步线程),异步线程不可能是当前的调用线程,因为那样仍然会造成调用线程的阻塞,与同步无异。事实上,.NET会将所有的异步请求队列加入线程池,以线程池内的线程处理所有的异步请求。

以上就是一个简单的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 中为 ByRef 和 ByRef)以及由 BeginInvoke 返回的 IAsyncResult。

注意   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 中为 ByRef),它演示了如何将这些参数添加到 BeginInvoke 和 EndInvoke 的签名中。您可以用类似的方式处理 ref 参数(在 Visual Basic 中为 ByRef)。

下面的代码示例显示 TestMethod 以及代表 TestMethod 的委托;若要使用任一示例,请将示例代码追加到这段代码中。

注意   为了简化这些示例,TestMethod 在独立于 Main() 的类中声明。或者,TestMethod 可以是包含 Main() 的同一类中的 static 方法(在 Visual Basic 中为 Shared)。

  1. using System;  
  2. using System.Threading;   
  3.  
  4. public class AsyncDemo {  
  5. // The method to be executed asynchronously.  
  6. //  
  7. public string TestMethod(  
  8. int callDuration, out int threadId) {  
  9. Console.WriteLine("Test method begins.");  
  10. Thread.Sleep(callDuration);  
  11. threadId = AppDomain.GetCurrentThreadId();  
  12. return "MyCallTime was " + callDuration.ToString();  
  13. }  
  14. }  
  15.  
  16. // The delegate must have the same signature as the method  
  17. // you want to call asynchronously.  
  18. public delegate string AsyncDelegate(  
  19. int callDuration, out int threadId);  
  20.    
  21.  
  22. using System;  
  23. using System.Threading;   
  24.  
  25. public class AsyncDemo {  
  26. // The method to be executed asynchronously.  
  27. //  
  28. public string TestMethod(  
  29. int callDuration, out int threadId) {  
  30. Console.WriteLine("Test method begins.");  
  31. Thread.Sleep(callDuration);  
  32. threadId = AppDomain.GetCurrentThreadId();  
  33. return "MyCallTime was " + callDuration.ToString();  
  34. }  
  35. }  
  36.  
  37. // The delegate must have the same signature as the method  
  38. // you want to call asynchronously.  
  39. public delegate string AsyncDelegate(  
  40. int callDuration, out int threadId); 

C#异步调用四大方法之使用 EndInvoke 等待异步调用

异步执行方法的最简单方式是以 BeginInvoke 开始,对主线程执行一些操作,然后调用 EndInvoke。EndInvoke 直到C#异步调用完成后才返回。这种技术非常适合文件或网络操作,但是由于它阻塞 EndInvoke,所以不要从用户界面的服务线程中使用它。

  1. public class AsyncMain {  
  2. static void Main(string[] args) {  
  3. // The asynchronous method puts the thread id here.  
  4. int threadId;  
  5.  
  6. // Create an instance of the test class.  
  7. AsyncDemo ad = new AsyncDemo();  
  8.  
  9. // Create the delegate.  
  10. AsyncDelegate dlgt = new AsyncDelegate(ad.TestMethod);  
  11.      
  12. // Initiate the asychronous call.  
  13. IAsyncResult ar = dlgt.BeginInvoke(3000,   
  14. out threadId, nullnull);  
  15.  
  16. Thread.Sleep(0);  
  17. Console.WriteLine("Main thread {0} does some work.",  
  18. AppDomain.GetCurrentThreadId());  
  19.  
  20. // Call EndInvoke to Wait for   
  21. //the asynchronous call to complete,  
  22. // and to retrieve the results.  
  23. string ret = dlgt.EndInvoke(out threadId, ar);  
  24.  
  25. Console.WriteLine("The call executed on thread {0},   
  26. with return value \"{1}\".", threadId, ret);  
  27. }  

C#异步调用四大方法之使用 WaitHandle 等待异步调用

等待 WaitHandle 是一项常用的线程同步技术。您可以使用由 BeginInvoke 返回的 IAsyncResult 的 AsyncWaitHandle 属性来获取 WaitHandle。C#异步调用完成时会发出 WaitHandle 信号,而您可以通过调用它的 WaitOne 等待它。

如果您使用 WaitHandle,则在C#异步调用完成之后,但在通过调用 EndInvoke 检索结果之前,可以执行其他处理。

  1. public class AsyncMain {  
  2. static void Main(string[] args) {  
  3. // The asynchronous method puts the thread id here.  
  4. int threadId;  
  5.  
  6. // Create an instance of the test class.  
  7. AsyncDemo ad = new AsyncDemo();  
  8.  
  9. // Create the delegate.  
  10. AsyncDelegate dlgt = new AsyncDelegate(ad.TestMethod);  
  11.      
  12. // Initiate the asychronous call.  
  13. IAsyncResult ar = dlgt.BeginInvoke(3000,   
  14. out threadId, nullnull);  
  15.  
  16. Thread.Sleep(0);  
  17. Console.WriteLine("Main thread {0} does some work.",  
  18. AppDomain.GetCurrentThreadId());  
  19.  
  20. // Wait for the WaitHandle to become signaled.  
  21. ar.AsyncWaitHandle.WaitOne();  
  22.  
  23. // Perform additional processing here.  
  24. // Call EndInvoke to retrieve the results.  
  25. string ret = dlgt.EndInvoke(out threadId, ar);  
  26.  
  27. Console.WriteLine("The call executed on thread {0},   
  28. with return value \"{1}\".", threadId, ret);  
  29. }  

C#异步调用四大方法之轮询异步调用完成

您可以使用由 BeginInvoke 返回的 IAsyncResult 的 IsCompleted 属性来发现C#异步调用何时完成。从用户界面的服务线程中进行C#异步调用时可以执行此操作。轮询完成允许用户界面线程继续处理用户输入。

  1. public class AsyncMain {  
  2. static void Main(string[] args) {  
  3. // The asynchronous method puts the thread id here.  
  4. int threadId;  
  5.  
  6. // Create an instance of the test class.  
  7. AsyncDemo ad = new AsyncDemo();  
  8.  
  9. // Create the delegate.  
  10. AsyncDelegate dlgt = new AsyncDelegate(ad.TestMethod);  
  11.      
  12. // Initiate the asychronous call.  
  13. IAsyncResult ar = dlgt.BeginInvoke(3000,   
  14. out threadId, nullnull);  
  15.  
  16. // Poll while simulating work.  
  17. while(ar.IsCompleted == false) {  
  18. Thread.Sleep(10);  
  19. }  
  20.  
  21. // Call EndInvoke to retrieve the results.  
  22. string ret = dlgt.EndInvoke(out threadId, ar);  
  23.  
  24. Console.WriteLine("The call executed on thread {0},  
  25.  with return value \"{1}\".", threadId, ret);  
  26. }  

C#异步调用四大方法之异步调用完成时执行回调方法

如果启动异步调用的线程不需要处理调用结果,则可以在调用完成时执行回调方法。回调方法在 ThreadPool 线程上执行。

要使用回调方法,必须将代表该方法的 AsyncCallback 委托传递给 BeginInvoke。也可以传递包含回调方法将要使用的信息的对象。例如,可以传递启动调用时曾使用的委托,以便回调方法能够调用 EndInvoke。

  1. public class AsyncMain {  
  2. // Asynchronous method puts the thread id here.  
  3. private static int threadId;  
  4.  
  5. static void Main(string[] args) {  
  6. // Create an instance of the test class.  
  7. AsyncDemo ad = new AsyncDemo();  
  8.  
  9. // Create the delegate.  
  10. AsyncDelegate dlgt = new AsyncDelegate(ad.TestMethod);  
  11.      
  12. // Initiate the asychronous call.  Include an AsyncCallback  
  13. // delegate representing the callback method, and the data  
  14. // needed to call EndInvoke.  
  15. IAsyncResult ar = dlgt.BeginInvoke(3000,  
  16. out threadId,   
  17. new AsyncCallback(CallbackMethod),  
  18. dlgt );  
  19.  
  20. Console.WriteLine("Press Enter to close application.");  
  21. Console.ReadLine();  
  22. }  
  23.  
  24. // Callback method must have the same signature as the  
  25. // AsyncCallback delegate.  
  26. static void CallbackMethod(IAsyncResult ar) {  
  27. // Retrieve the delegate.  
  28. AsyncDelegate dlgt = (AsyncDelegate) ar.AsyncState;  
  29.  
  30. // Call EndInvoke to retrieve the results.  
  31. string ret = dlgt.EndInvoke(out threadId, ar);  
  32.  
  33. Console.WriteLine("The call executed on thread {0},  
  34.  with return value \"{1}\".", threadId, ret);  
  35. }  
  36. }

原创粉丝点击