智慧型客戶端呼叫 Web Service 或長時間運算時,若阻塞 UI 執行緒會導致介面凍結。.NET Framework 雖提供 Begin/End 非同步模式(APM),但需手動處理 Control.Invoke 切回 UI 執行緒,程式碼冗長且易出錯。本文提出「自動回呼」模式,將執行緒切換與委派建立封裝於基底類別,讓 UI 程式碼僅需呼叫 BeginXxx 並實作 XxxCompleted 即可。
// UI 事件處理
_proxy.BeginGetCustomerData("Joe", new AsyncCallback(CustomerDataCallback), null);
// 回呼在背景執行緒
void CustomerDataCallback(IAsyncResult ar) {
var data = _proxy.EndGetCustomerData(ar);
// 必須手動切回 UI 執行緒
this.Invoke(new EventHandler(UpdateUI), null, null);
}
void UpdateUI(object s, EventArgs e) { dataGrid.DataSource = data; }
public abstract class AutoCallbackServiceAgent {
private readonly object _callbackTarget;
protected AutoCallbackServiceAgent(object callbackTarget) { _callbackTarget = callbackTarget; }
protected void InvokeAutoCallback(string methodName, object[] args, Type delegateType) {
var callback = Delegate.CreateDelegate(delegateType, _callbackTarget, methodName);
var ctrl = _callbackTarget as Control;
if (ctrl != null && ctrl.InvokeRequired)
ctrl.Invoke(callback, args); // 切回 UI 執行緒
else
callback.DynamicInvoke(args); // 已在正確執行緒或非 UI 物件
}
}
public class CustomerServiceAgent : AutoCallbackServiceAgent {
private readonly CustomerWebService _proxy = new CustomerWebService();
private delegate void GetCustomerDataCompletedCallback(DataSet data);
public CustomerServiceAgent(object callbackTarget) : base(callbackTarget) {}
public void BeginGetCustomerData(string id) {
_proxy.BeginGetCustomerData(id, new AsyncCallback(GetCustomerDataCallback), null);
}
private void GetCustomerDataCallback(IAsyncResult ar) {
var data = _proxy.EndGetCustomerData(ar);
InvokeAutoCallback("GetCustomerDataCompleted", new object[]{data}, typeof(GetCustomerDataCompletedCallback));
}
}
private CustomerServiceAgent _agent = new CustomerServiceAgent(this);
private void btnLoad_Click(object sender, EventArgs e) {
_agent.BeginGetCustomerData("Joe"); // 一行啟動
}
private void GetCustomerDataCompleted(DataSet data) { // 自動在 UI 執行緒
dataGrid.DataSource = data;
}
delegate void AutoCallback(object[] args),犧牲型別安全換取基底類別零委派定義。InvokeAutoCallback 參數中攜帶 Exception 物件,UI 端統一處理失敗情境。此模式將「非同步啟動、執行緒切換、結果回呼」三大責任清晰分離,符合單一職責原則,大幅降低 UI 開發複雜度。