异步 需要用的地方挺多,有必要总结一下。
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#中的async和wait等待关键字,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
// 订阅者 方法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
OnEvent += new EventHandler
// 发布者
//下面的代码实现事件的异步调用
//获取事件中的多路委托列表
Delegate[] delegAry = OnEvent.GetInvocationList();
//遍历委托列表
foreach (EventHandler
{
//异步调用委托
第一个参数为要回调的函数(执行完自身的方法后会继续执行的方法),第二个参数为要向回调函数传入的值(自身的委托,方便在回调函数中获取执行完返回的信息)
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");
}
}
结果:
但以上的方法是同步的,也就是第一个方法执行太久的话会影响第二个方法的执行,那么要解决这问题,下面就用到异步委托。
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[] delegAry = Output.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();
}
子线程操作主线程的控件还有其他方法,大家可以尝试下,这里就不整理了。
结果: