EAP(Event-based Asynchronous Pattern) 是基於事件的非同步模式,在 .NET Framework 2.0 中引入。EAP 需要一個有 Async 尾碼方法和一個或多個事件。EAP不再推薦用於新開發 ...
EAP(Event-based Asynchronous Pattern) 是基於事件的非同步模式,在 .NET Framework 2.0 中引入。EAP 需要一個有 Async 尾碼方法和一個或多個事件。EAP不再推薦用於新開發。
EAP
一個符合 EAP 模式的示例聲明如下:
public class AsyncExample
{
// Synchronous methods.
public int Method1(string param);
public void Method2(double param);
// Asynchronous methods.
public void Method1Async(string param);
public void Method1Async(string param, object userState);
public event Method1CompletedEventHandler Method1Completed;
public void Method2Async(double param);
public void Method2Async(double param, object userState);
public event Method2CompletedEventHandler Method2Completed;
public void CancelAsync(object userState);
public bool IsBusy { get; }
// Class implementation not shown.
}
BackgroundWorker
SoundPlayer和PictureBox組件表示基於事件的非同步模式的簡單實現。WebClient和BackgroundWorker組件代表了基於事件的非同步模式的更複雜的實現。
private void EAP_Btn_Click(object sender, RoutedEventArgs e)
{
// 記錄時間
Debug.WriteLine("1-" + DateTime.Now.TimeOfDay.ToString() +
",ThreadID = " + Thread.CurrentThread.ManagedThreadId);
BackgroundWorker worker = new BackgroundWorker();
// 事件綁定
worker.DoWork += Worker_DoWork;
worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
// 非同步執行
worker.RunWorkerAsync();
// 記錄時間
Debug.WriteLine("4-" + DateTime.Now.TimeOfDay.ToString() +
",ThreadID = " + Thread.CurrentThread.ManagedThreadId);
}
private void Worker_RunWorkerCompleted(object? sender, RunWorkerCompletedEventArgs e)
{
// 記錄時間
Debug.WriteLine("3-" + DateTime.Now.TimeOfDay.ToString() +
",ThreadID = " + Thread.CurrentThread.ManagedThreadId);
}
private void Worker_DoWork(object? sender, DoWorkEventArgs e)
{
// 訪問外網網站網站
var req = WebRequest.Create("https://docs.newrelic.com/docs/apm/agents/net-agent/getting-started/net-agent-compatibility-requirements-net-framework/");
req.GetResponse();
Debug.WriteLine("2-" + DateTime.Now.TimeOfDay.ToString() +
",ThreadID = " + Thread.CurrentThread.ManagedThreadId);
}
程式運行效果:
日誌輸出:
1-17:00:10.3584040,ThreadID = 1
4-17:00:10.3609798,ThreadID = 1
2-17:00:11.3485887,ThreadID = 8
3-17:00:11.3514632,ThreadID = 1
從效果和日誌上看:
- EAP 不會阻塞調用線程
- 非同步操作真正執行是在另外一個線程
- RunWorkerCompleted 回調會回到調用線程(UI線程)
和APM比起來很像,好像只是把 委托綁定 放到了外面。
我們可以看一下 BackgroundWorker 的源碼:
- 在構造函數里實例化一個委托 threadStart
- 調用 RunWorkerAsync() 方法
看起來 EAP 的本質,還是使用了委托的非同步方式(BeginInvoke),實質上還是 APM 非同步模式。
多任務
如果有多個非同步任務,我們希望按照先後順序執行,並且需要在調用線程上得到所有返回值。
public Func<string, string> func1()
{
return new Func<string, string>(t =>
{
Thread.Sleep(2000);
return "name:" + t;
});
}
public Func<string, string> func2()
{
return new Func<string, string>(t =>
{
Thread.Sleep(2000);
return "age:" + t;
});
}
public Func<string, string> func3()
{
return new Func<string, string>(t =>
{
Thread.Sleep(2000);
return "sex:" + t;
});
}
// 按照一定的順序去執行
public void Multi_APM_Btn_Click(object sender, RoutedEventArgs e)
{
string str1 = string.Empty, str2 = string.Empty, str3 = string.Empty;
IAsyncResult asyncResult1 = null, asyncResult2 = null, asyncResult3 = null;
asyncResult1 = func1().BeginInvoke("張三", t =>
{
str1 = func1().EndInvoke(t);
Debug.WriteLine("1-" + DateTime.Now.TimeOfDay.ToString() +",ThreadID = " + Thread.CurrentThread.ManagedThreadId);
asyncResult2 = func2().BeginInvoke("26", a =>
{
str2 = func2().EndInvoke(a);
Debug.WriteLine("2-" + DateTime.Now.TimeOfDay.ToString() +",ThreadID = " + Thread.CurrentThread.ManagedThreadId);
asyncResult3 = func3().BeginInvoke("男", s =>
{
str3 = func3().EndInvoke(s);
Debug.WriteLine("3-" + DateTime.Now.TimeOfDay.ToString() + ",ThreadID = " + Thread.CurrentThread.ManagedThreadId);
}, null);
}, null);
}, null);
asyncResult1.AsyncWaitHandle.WaitOne();
asyncResult2.AsyncWaitHandle.WaitOne();
asyncResult3.AsyncWaitHandle.WaitOne();
Debug.WriteLine(str1 + str2 + str3);
}
運行起來,發現有異常:
由此可見在完成第一個非同步操作之前沒有對asyncResult2進行賦值,asyncResult2執行非同步等待的時候報異常。也可以有其他方法來解決這個問題,但會比較複雜。