天天看點

[C#基礎]c#中的BeginInvoke和EndEndInvoke

摘要

異步這東西,真正用起來的時候,發現事情還是挺多的,最近在項目中用到了異步的知識,發現對它還是不了解,處理起來,走了不少彎路。覺得還是補一補還是很有必要的。

MSDN原文位址:https://msdn.microsoft.com/en-us/library/2e08f6yc(v=vs.110).aspx

正文

.Net framework可以讓你異步調用任何方法。為達這樣的目的,你可以定義一個與你要調用的方法的簽名相同的委托。公共語言運作時将自動為該委托定義與簽名相同的BeginInvok和EndInvoke方法。

異步委托調用BeginInvok和EndInvoke方法,但在.NET Compact Framework中并不支援。

BeginInvoke方法觸發你的異步方法,它和你想要執行的異步方法有相同的參數。另外還有兩個可選參數,第一個是AsyncCallback委托是異步完成的回調方法。第二個是使用者自定義對象,該對象将傳遞到回調方法中。BeginInvoke立即傳回并且不等待完成異步的調用(繼續執行該下面的代碼,不需要等待)。BeginInvoke傳回IAsyncResult接口,可用于檢測異步調用的過程。

通過EndInvoke方法檢測異步調用的結果。如果異步調用尚未完成,EndInvoke将阻塞調用線程,直到它完成。EndInvoke參數包括out和ref參數。

下面代碼示範使用BeginInvoke和EndInvoke進行異步調用的四種常見方式。在調用BeginInvoke可以做以下工作:

  1. 做一些其他操作,然後調用EndInvoke方法阻塞線程直到該方法完成。
  2. 使用IAsyncResult.AsyncWaitHandle屬性,使用它的WaitOne方法阻塞線程直到收到WaitHandle信号,然後調用EndInvoke。
  3. 檢查BeginInvoke傳回值IAsyncResult的狀态來決定方法是否完成,然後調用EndInvoke方法。
  4. 通過在BeginInvoke方法中傳遞該委托,在回調方法中調用該委托的EenInvoke方法。

注意

無論你怎麼使用,都必須調用EndInvoke方法結束你的異步調用。

下面通過模拟一個耗時的操作,實作上面說的那四種情況。

情況一:通過EndInovke阻塞線程,直到異步調用結束。

[C#基礎]c#中的BeginInvoke和EndEndInvoke
[C#基礎]c#中的BeginInvoke和EndEndInvoke
using System;
using System.Diagnostics;
using System.Threading;

namespace BeginInvokeDemo
{
    /// <summary>
    /// 委托必須和要調用的異步方法有相同的簽名
    /// </summary>
    /// <param name="callDuration">sleep時間</param>
    /// <param name="threadId">目前線程id</param>
    /// <returns></returns>
    public delegate string AsyncMethodCaller(int callDuration, out int threadId);
    class Program
    {

        /// <summary>
        /// 主函數
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            AsyncMethodCaller caller = new AsyncMethodCaller(TestMethodAsync);
            int threadid = 0;
            //開啟異步操作
            IAsyncResult result = caller.BeginInvoke(3000, out threadid, null, null);
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("其它業務" + i.ToString());
            }
            //調用EndInvoke,等待異步執行完成
            Console.WriteLine("等待異步方法TestMethodAsync執行完成");
            string res = caller.EndInvoke(out threadid, result);
            Console.WriteLine("Completed!");
            Console.WriteLine(res);
            Console.Read();
        }
        /// <summary>
        /// 與委托對應的方法
        /// </summary>
        /// <param name="callDuration"></param>
        /// <param name="threadId"></param>
        /// <returns></returns>
        static string TestMethodAsync(int callDuration, out int threadId)
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            Console.WriteLine("異步TestMethodAsync開始");
            for (int i = 0; i < 5; i++)
            {   // 模拟耗時操作
                Thread.Sleep(callDuration);
                Console.WriteLine("TestMethodAsync:" + i.ToString());
            }
            sw.Stop();
            threadId = Thread.CurrentThread.ManagedThreadId;
            return string.Format("耗時{0}ms.", sw.ElapsedMilliseconds.ToString());
        }
    }
}      

View Code

結果

[C#基礎]c#中的BeginInvoke和EndEndInvoke

由上圖,可以看出,在BeginInvoke開啟異步執行方法,會先執行其他的業務。通過EndInvoke方法,阻塞直到異步執行完畢,才會執行EndInvoke之後的代碼。

情況二:通過WaitHandle屬性阻塞線程。

你可以獲得BeginInvoke的傳回值的WaitHandle,并使用它的AsyncWaitHanlde屬性。WaitHandle信号異步完成時,你可以通過調用WaitOne方法等待。

如果你使用WaitHandle,你可以在之前或者異步調用完成後進行其他的操作。但之後必須使用EndInvoke檢查結果。

當你調用EndInvoke方法時,等待句柄并不會自動關閉。如果你釋放等待處理的所有引用,當垃圾回收等待句柄是,系統資源将被釋放。一旦你完成使用等待句柄,通過WaitHandle的close方法,一次性顯示關閉,這時的垃圾回收效率更高。
using System;
using System.Diagnostics;
using System.Threading;

namespace BeginInvokeDemo
{
    /// <summary>
    /// 委托必須和要調用的異步方法有相同的簽名
    /// </summary>
    /// <param name="callDuration">sleep時間</param>
    /// <param name="threadId">目前線程id</param>
    /// <returns></returns>
    public delegate string AsyncMethodCaller(int callDuration, out int threadId);
    class Program
    {

        /// <summary>
        /// 主函數
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            AsyncMethodCaller caller = new AsyncMethodCaller(TestMethodAsync);
            int threadid = 0;
            //開啟異步操作
            IAsyncResult result = caller.BeginInvoke(3000, out threadid, null, null);
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("其它業務" + i.ToString());
            }
            //調用EndInvoke,等待異步執行完成
            Console.WriteLine("等待異步方法TestMethodAsync執行完成");
            //等待異步執行完畢信号
            result.AsyncWaitHandle.WaitOne();
            Console.WriteLine("收到WaitHandle信号");
            //通過EndInvoke檢查結果
            string res = caller.EndInvoke(out threadid, result);
            //顯示關閉句柄
            result.AsyncWaitHandle.Close();
            Console.WriteLine("關閉了WaitHandle句柄");
            Console.WriteLine("Completed!");
            Console.WriteLine(res);
            Console.Read();
        }
        /// <summary>
        /// 與委托對應的方法
        /// </summary>
        /// <param name="callDuration"></param>
        /// <param name="threadId"></param>
        /// <returns></returns>
        static string TestMethodAsync(int callDuration, out int threadId)
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            Console.WriteLine("異步TestMethodAsync開始");
            for (int i = 0; i < 5; i++)
            {   // 模拟耗時操作
                Thread.Sleep(callDuration);
                Console.WriteLine("TestMethodAsync:" + i.ToString());
            }
            sw.Stop();
            threadId = Thread.CurrentThread.ManagedThreadId;
            return string.Format("耗時{0}ms.", sw.ElapsedMilliseconds.ToString());
        }
    }
}      

輸出

[C#基礎]c#中的BeginInvoke和EndEndInvoke

情況三:檢查BeginInvoke傳回結果的狀态。

可以通過BeginInvoke的傳回結果的IsCompleted屬性檢查異步是否完成。你可以在異步沒有完成的時候做其他的操作。

[C#基礎]c#中的BeginInvoke和EndEndInvoke
[C#基礎]c#中的BeginInvoke和EndEndInvoke
using System;
using System.Diagnostics;
using System.Threading;

namespace BeginInvokeDemo
{
    /// <summary>
    /// 委托必須和要調用的異步方法有相同的簽名
    /// </summary>
    /// <param name="callDuration">sleep時間</param>
    /// <param name="threadId">目前線程id</param>
    /// <returns></returns>
    public delegate string AsyncMethodCaller(int callDuration, out int threadId);
    class Program
    {

        /// <summary>
        /// 主函數
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            AsyncMethodCaller caller = new AsyncMethodCaller(TestMethodAsync);
            int threadid = 0;
            //開啟異步操作
            IAsyncResult result = caller.BeginInvoke(1000, out threadid, null, null);
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("其它業務" + i.ToString());
            }
            //調用EndInvoke,等待異步執行完成
            Console.WriteLine("等待異步方法TestMethodAsync執行完成");
            //等待異步執行完畢信号
            //result.AsyncWaitHandle.WaitOne();
            //Console.WriteLine("收到WaitHandle信号");
            //通過循環不停的檢查異步運作狀态
            while (result.IsCompleted==false)
            {
                Thread.Sleep(100);
                Console.WriteLine("異步方法,running........");
            }
            //異步結束,拿到運作結果
            string res = caller.EndInvoke(out threadid, result);
            //顯示關閉句柄
            result.AsyncWaitHandle.Close();
            Console.WriteLine("關閉了WaitHandle句柄");
            Console.WriteLine("Completed!");
            Console.WriteLine(res);
            Console.Read();
        }
        /// <summary>
        /// 與委托對應的方法
        /// </summary>
        /// <param name="callDuration"></param>
        /// <param name="threadId"></param>
        /// <returns></returns>
        static string TestMethodAsync(int callDuration, out int threadId)
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            Console.WriteLine("異步TestMethodAsync開始");
            for (int i = 0; i < 5; i++)
            {   // 模拟耗時操作
                Thread.Sleep(callDuration);
                Console.WriteLine("TestMethodAsync:" + i.ToString());
            }
            sw.Stop();
            threadId = Thread.CurrentThread.ManagedThreadId;
            return string.Format("耗時{0}ms.", sw.ElapsedMilliseconds.ToString());
        }
    }
}      
其它業務0
其它業務1
其它業務2
其它業務3
其它業務4
其它業務5
其它業務6
其它業務7
其它業務8
其它業務9
等待異步方法TestMethodAsync執行完成
異步TestMethodAsync開始
異步方法,running........
異步方法,running........
異步方法,running........
異步方法,running........
異步方法,running........
異步方法,running........
異步方法,running........
異步方法,running........
TestMethodAsync:0
異步方法,running........
異步方法,running........
異步方法,running........
異步方法,running........
異步方法,running........
異步方法,running........
異步方法,running........
異步方法,running........
異步方法,running........
異步方法,running........
TestMethodAsync:1
異步方法,running........
異步方法,running........
異步方法,running........
異步方法,running........
異步方法,running........
異步方法,running........
異步方法,running........
異步方法,running........
異步方法,running........
異步方法,running........
TestMethodAsync:2
異步方法,running........
異步方法,running........
異步方法,running........
異步方法,running........
異步方法,running........
異步方法,running........
異步方法,running........
異步方法,running........
異步方法,running........
異步方法,running........
TestMethodAsync:3
異步方法,running........
異步方法,running........
異步方法,running........
異步方法,running........
異步方法,running........
異步方法,running........
異步方法,running........
異步方法,running........
異步方法,running........
異步方法,running........
TestMethodAsync:4
異步方法,running........
關閉了WaitHandle句柄
Completed!
耗時5031ms.      

情況四:通過在回調方法中。

如果需要在異步完成後需要做一些其他的操作,你可以在異步完成時執行一個回調方法。在該回調方法中做處理。

首先需要定義一個回調方法。

using System;
using System.Diagnostics;
using System.Threading;

namespace BeginInvokeDemo
{
    /// <summary>
    /// 委托必須和要調用的異步方法有相同的簽名
    /// </summary>
    /// <param name="callDuration">sleep時間</param>
    /// <param name="threadId">目前線程id</param>
    /// <returns></returns>
    public delegate string AsyncMethodCaller(int callDuration, out int threadId);
    class Program
    {

        /// <summary>
        /// 主函數
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            AsyncMethodCaller caller = new AsyncMethodCaller(TestMethodAsync);
            int threadid = 0;
            //開啟異步操作
            IAsyncResult result = caller.BeginInvoke(1000, out threadid, callBackMethod, caller);
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("其它業務" + i.ToString());
            }
            //調用EndInvoke,等待異步執行完成
            Console.WriteLine("等待異步方法TestMethodAsync執行完成");
            //等待異步執行完畢信号
            //result.AsyncWaitHandle.WaitOne();
            //Console.WriteLine("收到WaitHandle信号");
            //通過循環不停的檢查異步運作狀态
            //while (result.IsCompleted==false)
            //{
            //    Thread.Sleep(100);
            //    Console.WriteLine("異步方法,running........");
            //}
            //異步結束,拿到運作結果
            //string res = caller.EndInvoke(out threadid, result);
            ////顯示關閉句柄
            //result.AsyncWaitHandle.Close();
            Console.WriteLine("關閉了WaitHandle句柄");

            //Console.WriteLine(res);
            Console.Read();
        }
        /// <summary>
        /// 異步方法回調方法,異步執行完畢,會回調該方法
        /// </summary>
        /// <param name="ar"></param>
        private static void callBackMethod(IAsyncResult ar)
        {
            AsyncMethodCaller caller = ar.AsyncState as AsyncMethodCaller; 
            string result = caller.EndInvoke(out int threadid, ar);
            Console.WriteLine("Completed!");
            Console.WriteLine(result);

        }

        /// <summary>
        /// 與委托對應的方法
        /// </summary>
        /// <param name="callDuration"></param>
        /// <param name="threadId"></param>
        /// <returns></returns>
        static string TestMethodAsync(int callDuration, out int threadId)
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            Console.WriteLine("異步TestMethodAsync開始");
            for (int i = 0; i < 5; i++)
            {   // 模拟耗時操作
                Thread.Sleep(callDuration);
                Console.WriteLine("TestMethodAsync:" + i.ToString());
            }
            sw.Stop();
            threadId = Thread.CurrentThread.ManagedThreadId;
            return string.Format("耗時{0}ms.", sw.ElapsedMilliseconds.ToString());
        }
    }
}      
[C#基礎]c#中的BeginInvoke和EndEndInvoke

總結

在項目中遇到的有參數又傳回值的情況,如何在回調方法中拿到委托和傳遞的參數,當時卡這裡了,後來檢視情況四的情況,才得以解決。這裡也再學習一下。

  • 部落格位址:http://www.cnblogs.com/wolf-sun/

    部落格版權:如果文中有不妥或者錯誤的地方還望高手的你指出,以免誤人子弟。如果覺得本文對你有所幫助不如【推薦】一下!如果你有更好的建議,不如留言一起讨論,共同進步!

    再次感謝您耐心的讀完本篇文章。