異步 需要用的地方挺多,有必要總結一下。
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<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[] delegAry = Output.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();
}
子線程操作主線程的控件還有其他方法,大家可以嘗試下,這裏就不整理了。
結果: