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的定义如图:C# 异步事件调用委托_第1张图片

作用就是:作为执行调用的回调方法,值得注意的是,在回调方法中,必须调用EndInvoke方法结束异步调用,EndInvoke是获取异步调用的结果
上面的例子调试的结果如图: 

C# 异步事件调用委托_第2张图片

 

 

 

如何实现异步事件调用呢?事件其实是一种MulticastDelegate(多播委托)。而MulticastDelegate类提供了一个GetInvocationList方法,该方法返回此多播委托的委托调用数组。利用该方法就能实现我们的异步事件调用功能。

 

using System;

using System.Threading;

using System.Runtime.Remoting.Messaging;

namespace ProcessTest

{

    class Program

    {

        //定义一个事件

        public static event EventHandler 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(Method1);

            OnEvent += new EventHandler(Method2);

 

             // 发布者

            //下面的代码实现事件的异步调用

            //获取事件中的多路委托列表

            Delegate[] delegAry = OnEvent.GetInvocationList();

            //遍历委托列表

            foreach (EventHandler deleg in delegAry)

            {

                //异步调用委托

第一个参数为要回调的函数(执行完自身的方法后会继续执行的方法),第二个参数为要向回调函数传入的值(自身的委托,方便在回调函数中获取执行完返回的信息)

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

            }

            System.Console.ReadKey();

        }

    }

}

 

 Demo: 

三个页面:Observer.cs(观察者)、Subject.cs(通知者)、Form1.cs

 

Observer.cs (事件订阅者

class Observer
    {
        ///


        /// 执行事件A
        ///

        ///
        public string DoA()
        {
            return "时间到了,执行事件A~~";
        }

        ///


        ///执行事件B
        ///

        ///
        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");
            }
}

 

 结果:

 

 

C# 异步事件调用委托_第3张图片

但以上的方法是同步的,也就是第一个方法执行太久的话会影响第二个方法的执行,那么要解决这问题,下面就用到异步委托。

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);
                    
                }
            }
            
        }

        ///


        /// 回调函数
        ///

        ///
        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();
            
            
        }

 

 

子线程操作主线程的控件还有其他方法,大家可以尝试下,这里就不整理了。

结果:

C# 异步事件调用委托_第4张图片

 

你可能感兴趣的:(C#,设计模式与架构)