這個陷阱來自于一個需求:需要異步在后臺處理數據,處理完后觸發處理完成的事件,大概是這么寫的:
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
各位同學可以根據自己的需要添加類型安全的實現。
新聞熱點
疑難解答