C# 異步事件調用委託

異步 需要用的地方挺多,有必要總結一下。
msdn文檔:https://docs.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/
官方的簡介:
*.NET Framework提供了執行異步操作的三種模式:
異步編程模型(APM)模式(也稱爲IAsyncResult的模式),其中異步操作要求Begin和End方法(例如,BeginWrite和EndWrite異步寫入操作)。這種模式不再被推薦用於新開發。有關更多信息,請參閱異步編程模型(APM)。

基於事件的異步模式(EAP),它需要一個具有Async後綴的方法,並且還需要一個或多個事件,事件處理程序委託類型和被EventArg派生類型。EAP在.NET Framework 2.0中引入。不再推薦新的開發。有關更多信息,請參閱基於事件的異步模式(EAP)。

基於任務的異步模式(TAP),它使用單一方法來表示異步操作的啓動和完成。TAP在.NET Framework 4中引入,是.NET Framework中推薦的異步編程方法。C#中的asyncwait等待關鍵字,Visual Basic語言中的Async和Await運算符爲TAP添加語言支持。有關更多信息,請參閱基於任務的異步模式(TAP)。* Task-based Asynchronous Pattern (TAP), which uses a single method to represent the initiation and completion of an asynchronous operation. TAP was introduced in the .NET Framework 4. It's the recommended approach to asynchronous programming in .NET. 

 

方法一(不再被推薦用):使用回調方法完成異步委託

首先定義一個string類型的返回值、string類型的參數的委託

class Program
    {
        delegate string SayHi(string name);//定義委託
        static void Main(string[] args)
        {
            SayHi sayhi = new SayHi(SayHiName); //實例化委託
            sayhi("科比"); //一般的直接同步調用
            sayhi.Invoke("張林"); //使用Invoke方法同步調用


            //異步調用
            sayhi.BeginInvoke("杜蘭特", (IAsyncResult  ar) =>
            {

               //必須用EndInvoke()獲取異步調用的結果
                sayhi.EndInvoke(ar);
                Console.WriteLine("打招呼成功結束"); 
            }, null);
        }
        public static string SayHiName(string  name)
        {
            return "how are you"+name + "?";
        }
    }
 

前兩種調用委託的方式都是同步的,BeginInvoke方法的返回值是IAsyncResult類型的
該方法的參數由兩部分組成,前面(n)個參數是委託的參數,倒數第二個參數也表示一個委託,該委託是.net系統定義的委託(和func、action類似),查看AsyncCallback的定義如圖:

作用就是:作爲執行調用的回調方法,值得注意的是,在回調方法中,必須調用EndInvoke方法結束異步調用,EndInvoke是獲取異步調用的結果
上面的例子調試的結果如圖: 

 

 

 

如何實現異步事件調用呢?事件其實是一種MulticastDelegate(多播委託)。而MulticastDelegate類提供了一個GetInvocationList方法,該方法返回此多播委託的委託調用數組。利用該方法就能實現我們的異步事件調用功能。

 

using System;

using System.Threading;

using System.Runtime.Remoting.Messaging;

namespace ProcessTest

{

    class Program

    {

        //定義一個事件

        public static event EventHandler<EventArgs> OnEvent;

 

        // 訂閱者 方法1

        static void Method1(object sender, EventArgs e)

        {

            //顯示執行該方法的線程ID

            Console.WriteLine("調用Method1的線程ID爲:{0}", Thread.CurrentThread.ManagedThreadId);

            Thread.Sleep(1000);

        }

        // 訂閱者 方法2

        static void Method2(object sender, EventArgs e)

        {

            Console.WriteLine("調用Method2的線程ID爲:{0}", Thread.CurrentThread.ManagedThreadId);

            Thread.Sleep(1000);

        }

        static void Main(string[] args)

        {

            //顯示主線程ID

            System.Console.WriteLine("主線程ID爲:{0}", Thread.CurrentThread.ManagedThreadId);

 

            // 事件訂閱

            //將Method1和Method2註冊到事件中

            OnEvent += new EventHandler<EventArgs>(Method1);

            OnEvent += new EventHandler<EventArgs>(Method2);

 

             // 發佈者

            //下面的代碼實現事件的異步調用

            //獲取事件中的多路委託列表

            Delegate[] delegAry = OnEvent.GetInvocationList();

            //遍歷委託列表

            foreach (EventHandler<EventArgs> deleg in delegAry)

            {

                //異步調用委託

第一個參數爲要回調的函數(執行完自身的方法後會繼續執行的方法),第二個參數爲要向回調函數傳入的值(自身的委託,方便在回調函數中獲取執行完返回的信息)

                deleg.BeginInvoke(null, EventArgs.Empty, null, null);

            }

            System.Console.ReadKey();

        }

    }

}

 

 Demo: 

三個頁面:Observer.cs(觀察者)、Subject.cs(通知者)、Form1.cs

 

Observer.cs (事件訂閱者

class Observer
    {
        /// <summary>
        /// 執行事件A
        /// </summary>
        /// <returns></returns>
        public string DoA()
        {
            return "時間到了,執行事件A~~";
        }

        /// <summary>
        ///執行事件B
        /// </summary>
        /// <returns></returns>
        public string DoB()
        {
            return "時間到了,執行事件B~~";
        }

    }

 

 

Subject.cs(事件發佈者)

namespace XXXXXX
{
    //聲明委託
    delegate string EventHandler();
    class Subject
    {
        //聲明事件
        public event EventHandler Output;

       // 觸發執行事件
        public string Notify()
        {
            string res = "";
            if (Output != null)
            {
                res = Output();
            }
            return res;
        }
  }
}

 

 

Form1.cs (事件訂閱觸發

使用了TextBox控件txtShow和Timer控件timer,timer的時間間隔設爲1s

private void timer_Tick(object sender, EventArgs e)
        {
            
           Subject subject = new Subject();
            Observer observer = new Observer();
            string now = DateTime.Now.ToString("HH:mm:ss");

           // 事件註冊(訂閱)
           // 訂閱者自己的處理方法,向發佈者進行委託事件訂閱
           //設置固定時間要執行的事件
            switch (now)
            {
                case "22:28:00":
                    subject.Output += new EventHandler(observer.DoA);
                    break;
                case "22:29:00":
                    subject.Output += new EventHandler(observer.DoB);
                    break;
            }
            string res = "";
            //觸發執行事件
            res += subject.Notify();
            if (res != "")
            {
                txtShow.AppendText(now + ":");
                txtShow.AppendText(res);
                txtShow.AppendText("\r\n");
            }
}

 

 結果:

 

 

但以上的方法是同步的,也就是第一個方法執行太久的話會影響第二個方法的執行,那麼要解決這問題,下面就用到異步委託。

Observer.cs不用修改到,這也是用了觀察者模式所帶來的好處。

Subject.cs(修改了Notify方法,添加了一個委託、事件和方法)

namespace XXXX
{
    //聲明委託
    delegate string EventHandler();
    delegate void ShowInfoHandler(string info);
    class Subject
    {
        //聲明事件
        public event EventHandler Output;
        public event ShowInfoHandler ShowInfoEvent;
       

        public void Notify()
        {
           
            if (Output != null)
            {

                //獲取事件中的多路委託列表

                Delegate[] delegAryOutput.GetInvocationList();

 

                //遍歷委託列表

                foreach( EventHandler handler in delegAry )
                {
                    //異步調用委託,第一個參數爲要回調的函數(執行完自身的方法後會繼續執行的方法),第二個參數爲要向回調函數傳入的值(自身的委託,方便在回調函數中獲取執行完返回的信息)
                    //這裏傳入被調用方法的委託
                    handler.BeginInvoke(CallBack handler);
                    
                }
            }
            
        }

        /// <summary>
        /// 回調函數
        /// </summary>
        /// <param name="show"></param>
        public void CallBack(IAsyncResult obj)
        {
            EventHandler handler = (EventHandler)obj.AsyncState;
            //獲取被調用方法的返回的信息
            string res= handler.EndInvoke(obj);
            ShowInfoEvent(res);
        }

    }
}

這裏稍微解釋一下。ShowInfoHandler、ShowInfoEvent用於向主線程txtShow輸出提示信息用的,若不用輸出提示信息可以省去。(Form1.cs會用到)

handler.BeginInvoke調用異步委託,第一個參數傳入要回調的函數,也就是執行完自身的方法後會繼續執行的方法;第二個參數一般傳入自身的委託,方便在回調函數中獲取執行完返回的信息。

 

Form1.cs

//非主線程無法操作界面的控件,所以用委託來實現向txtShow輸出信息
        public void ShowInfo(string info)
        {
            txtShow.Invoke(new Action(()=>{txtShow.AppendText(info+"\r\n");}));
        }

        private void timer_Tick(object sender, EventArgs e)
        {
            
            
            Subject subject = new Subject();
            Observer observer = new Observer();

            // 事件訂閱
            //將向訂閱者txtShow輸出信息的方法註冊交給subject委託 ShowInfoEvent
            subject.ShowInfoEvent += new ShowInfoHandler(this.ShowInfo);
            string now = DateTime.Now.ToString("HH:mm:ss");
            switch (now)
            {
                case "23:20:00":
                    txtShow.AppendText("現在時間:"+now+"\r\n");
                    subject.Output += new EventHandler(observer.DoA);
                    
                    break;
                case "23:21:00":
                    txtShow.AppendText("現在時間:"+now+"\r\n");
                    subject.Output += new EventHandler(observer.DoB);
                    break;
            }

            // 觸發事件
            subject.Notify();
            
            
        }

 

 

子線程操作主線程的控件還有其他方法,大家可以嘗試下,這裏就不整理了。

結果:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章