這個陷阱來自於一個需求:需要異步在後台處理數據,處理完後觸發處理完成的事件,大概是這麼寫的:
EmployeeCollection data = new EmployeeCollection(); data.Loaded += data_Loaded; Action<EmployeeCollection> action = (d) => { DalHelper.Fill(data); data.RaiseEventLoaded(); }; action.BeginInvoke(data, null, null);
挺簡單的代碼,陷阱也在其中。假如DalHelper.Fill(data)拋出了一個異常,那麼對data.RaiseEventLoaded()就不會執行,依賴於data.Loaded事件的代碼也不會執行,這是一個bug,應該在委托執行中加入一個try...catch語句,或者在某個地方調用委托的EndInvoke方法,來處理執行中可能的異常。
為了這麼一個簡單的需求,加入try...catch或者調用委托的EndInvoke都太復雜了,僅僅只想滿足假如執行失敗,就把異常拋出來,即使將當前進程結束也沒事。本著一次編寫,多次使用的原則,專門設計了一個幫助類來專職這類委托的異步調用。幫助類的代碼如下:
public class EventHelper { public static void UnsafeBeginInvoke(Delegate del,params object[] args){ AsyncFire asyncFire = InvokeDelegate; asyncFire.BeginInvoke(del, args, ThrowCallback, asyncFire); } delegate void AsyncFire(Delegate del,object[] args); static void InvokeDelegate(Delegate del,object[] args){ del.DynamicInvoke(args); } static void ThrowCallback(IAsyncResult ar) { AsyncFire asyncFire = ar.AsyncState as AsyncFire; asyncFire.EndInvoke(ar); } }
核心實現是將委托的調用封裝起來,在另外一個委托中去調用,然後對另外的那個委托用EndInvoke來釋放可能的異常,這樣就能夠發現單純的調用BeginInvoke後委托執行時引發的異常。這樣修改後,剛才的代碼就可以這樣來調用:
EmployeeCollection data = new EmployeeCollection(); data.Loaded += data_Loaded; Action<EmployeeCollection> action = (d) => { DalHelper.Fill(data); data.RaiseEventLoaded(); }; EventHelper.UnsafeBeginInvoke(action, data);
代碼還如最初的設計那麼簡單,而且真要是委托中發生了異常,也能夠發現這個錯誤,而不是讓這個錯誤被掩蓋。
另外,剛才的實現不是類型安全的,類型安全可以通過重載來解決,例子如下:
public class EventHelper { public static void UnsafeBeginInvoke(Delegate del,params object[] args){ AsyncFire asyncFire = InvokeDelegate; asyncFire.BeginInvoke(del, args, ThrowCallback, asyncFire); } delegate void AsyncFire(Delegate del,object[] args); static void InvokeDelegate(Delegate del,object[] args){ del.DynamicInvoke(args); } static void ThrowCallback(IAsyncResult ar) { AsyncFire asyncFire = ar.AsyncState as AsyncFire; asyncFire.EndInvoke(ar); } #region 添加類型安全的委托 public static void BeginInvoke(Action del){ UnsafeBeginInvoke(del); } public static void BeginInvoke<T,U>(Action<T,U> del,T t, U u){ UnsafeBeginInvoke(del,t,u); } public static void BeginInvoke<T,U,V>(Action<T,U> del,T t, U u, V v){ UnsafeBeginInvoke(del,t,u,v); } #endregion 添加類型安全的委托 } View Code各位同學可以根據自己的需要添加類型安全的實現。