[C#] 將同步方法改寫為非同步方法
前言
當我們遇到所需要花費較長時間的程式時,
我們通常會透過非同步的方式來呼叫它 (比如讀取資料流的BeginRead, EndRead),
那如果本來是同步的方法,我們有沒有辦法可以改寫它呢?
這篇的主題就是要來介紹如何將同步(sync)的程式改寫為非同步(async)的程式。
實際演練
在.Net Framework中,非同步模型(APM)大致上可以區分為兩類,
基於IAsyncResult的非同步模型,以及Event Based的非同步模型,
我們先來建立一個很簡單的加法函式,並且為它加入Thread.Sleep來模擬花費大量的時間,
public class Calculator
{
public int Calculate(int a, int b)
{
// 模擬花費大量時間運算
System.Threading.Thread.Sleep(10 * 1000);
return a + b;
}
}
通常在程式中執行此函式時看起來會像這樣,程式會卡在這邊非常久
Calculator calculator = new Calculator();
// 程式會停在這邊很長的時間
int result = calculator.Calculate(1, 2);
接下來,將介紹如何將這支函式改寫為可用非同步的方式呼叫
1. 改寫為基於IAsyncResult的非同步模型
首先,我們必須要先建立一個與函式Input和Output相同的delegate,
因為在3.5之後的版本已經有內建Func,所以在今天的範例就直接使用Func來當作delegate,
//In 2.0, sample, not work
//private delegate int CalculateDelegate(int a,int b);
//CalculateDelegate m_calculateDelegate = new CalculateDelegate(Calculate);
private Func<int, int, int> m_calculateDelegate;
之後我們可以仿照資料流讀取的BeginRead和EndRead一樣,分別撰寫BeginCalculate和EndCalculate方法,
public IAsyncResult BeginCalculate(int a, int b)
{
this.m_calculateDelegate = this.Calculate;
return this.m_calculateDelegate.BeginInvoke(a, b, null, null);
}
public int EndCalculate(IAsyncResult asyncResult)
{
return this.m_calculateDelegate.EndInvoke(asyncResult);
}
如此一來,我們就可以在程式中使用非同步的方法呼叫BeginCalculate和EndCalculate,
並且可以在程式計算完畢之前,繼續做其他的事情,
Calculator calculator = new Calculator();
IAsyncResult asyncResult = calculator.BeginCalculate(1, 2);
// Do something else
int result = calculator.EndCalculate(asyncResult);
2. 改寫為Event Based的非同步模型
Event Based的非同步模型,在執行完畢之後會將主控權交還給程式,
不會阻塞目前的Thread,一直到執行完畢之後,才會觸發註冊的事件。
首先我們建立一個當執行完畢時會觸發的事件,並且會傳入具有計算結果的EventArgs
public event EventHandler<CalculateFinishedEventArgs> CalculateFinished;
public class CalculateFinishedEventArgs : EventArgs
{
public int Result { get; private set; }
public CalculateFinishedEventArgs(int result)
{
this.Result = result;
}
}
再來我們建立一個供程式呼叫的方法CalculateAsync,在執行之後並不會阻塞Thread,
並且會在執行完畢之後,觸發CalculateFinished事件,
public void CalculateAsync(int a, int b)
{
this.m_calculateDelegate = Calculate;
AsyncCallback callback = new AsyncCallback(OnCalculateFinished);
this.m_calculateDelegate.BeginInvoke(a, b, callback, m_calculateDelegate);
}
private void OnCalculateFinished(IAsyncResult asyncResult)
{
Func calculateDelegate = (Func)asyncResult.AsyncState;
int result = calculateDelegate.EndInvoke(asyncResult);
EventHandler temp = CalculateFinished;
if (temp != null)
{
temp(this, new CalculateFinishedEventArgs(result));
}
}
執行程式就可改寫如下
Calculator calculator = new Calculator();
calculator.CalculateFinished += (o, args) => MessageBox.Show(string.Format("Result is {0}", args.Result));
calculator.CalculateAsync(1, 2);
// Do other works...
結語
為了有效的利用系統資源,並行作業,以及提供使用者良好的使用者體驗,
在撰寫程式時使用非同步的方法呼叫是不可或缺的,
但在撰寫非同步程式的同時,卻往往會造成程式碼較為零碎,
而比較不容易閱讀和維護的困擾,在下次的文章之中,
會來介紹如何使用Iterator來改善這種情況,
當然也可以利用之前介紹的非同步呼叫方法並跳出處理中視窗,
透過BackgroundWorker或是Thread的方式來處理,
也歡迎大家一起多多討論和指教囉 ^_^
參考資料:
1. http://msdn.microsoft.com/zh-tw/library/2e08f6yc(v=VS.100).aspx 以非同步的方式呼叫同步方法
2. http://msdn.microsoft.com/zh-tw/magazine/cc163467.aspx 實作 CLR 非同步程式撰寫模型