2010-06-14 141 views
2

快速問題:我的winform應用程序(c#)中的一個窗體對WCF服務進行異步調用以獲取一些數據。如果表單在回調發生之前恰好關閉,它會崩潰並出現有關訪問處置對象的錯誤。檢查/處理這種情況的正確方法是什麼?該錯誤發生在對該方法進行Invoke調用以更新我的表單,但我無法深入到內部異常,因爲它說代碼已被優化。C#處置異步回調

驗證碼:

 
    public void RequestUserPhoto(int userID) 
     { 
      WCF.Service.BeginGetUserPhoto(userID, 
       new AsyncCallback(GetUserPhotoCB), userID); 
     }

public void GetUserPhotoCB(IAsyncResult result) 
    { 
     var photo = WCF.Service.EndGetUserPhoto(result); 
     int userID = (int)result.AsyncState; 
     UpdateUserPhoto(userID, photo); 
    } 

    public delegate void UpdateUserPhotoDelegate(int userID, Binary photo); 
    public void UpdateUserPhoto(int userID, Binary photo) 
    { 
     if (InvokeRequired) 
     { 
      var d = new UpdateUserPhotoDelegate(UpdateUserPhoto); 
      Invoke(d, new object[] { userID, photo }); 
     } 
     else 
     { 
      if (photo != null) 
      { 
       var ms = new MemoryStream(photo.ToArray()); 
       var bmp = new System.Drawing.Bitmap(ms); 
       if (userID == theForm.AuthUserID) 
       { 
        pbMyPhoto.BackgroundImage = bmp; 
       } 
       else 
       { 
        pbPhoto.BackgroundImage = bmp; 
       } 
      } 
     } 
    } 

UPDATE:

我還是不知道哪裏去了這一點。這裏我真正需要的是一個設計模式,用於通過win窗體創建WCF異步服務調用,該窗體在異步調用返回之前可以處理優雅的窗體關閉。用戶可以隨時單擊表單上的X或任何形式的X.這個問題比上面顯示的單個例子要大得多。我的應用程序實際上會生成數百個WCF調用,並且我試圖找出如何在整個應用程序中以優雅的方式處理這些調用。例如,如果我必須爲每個WCF調用添加100行代碼與ManualResetEvents或後臺工作人員,或互斥體,或其他任何,只是這樣它不會炸燬我的應用程序,這將導致大量的錯誤空間。我需要的是一種乾淨的方式來異步調用服務,然後在表單發生關閉時將其轉換爲單向調用。換句話說,讓服務完成運行,但我不在乎結果是什麼,也不要叫回調,因爲它不再存在。

+0

請向我們顯示您的代碼。 – SLaks 2010-06-14 19:58:04

+0

查看更新的代碼。調用調用時發生崩潰(表示代碼已優化,不會提供任何詳細信息) – 2010-06-14 20:02:32

+0

是否沒有辦法跟蹤ASYNC調用並在form_closing事件中顯式取消它?或者,如果線程永遠不會返回,線程會永久停留在內存中? – 2010-07-02 12:59:22

回答

0

我對這個問題有很多好評,但沒有一個好的模式可以在我的應用程序中使用。我在這裏發佈我最終做的答案,並希望它能幫助其他人。

自動生成的WCF代理創建同步調用方法,使用begin/end模式的異步方法以及使用Completed事件的基於事件的委託。我上面在我原來的問題中發佈的例子使用了開始/結束模式。問題是,回調時,你將不得不做一個Invoke來訪問你的UI線程。如果該UI線程不再存在(I.E.用戶關閉了窗口),則會出現問題。新的基於事件的模式自動回到UI線程,從我的測試中,我無法在服務完成之前通過關閉來使其崩潰。我猜這個代理足夠聰明,如果內存地址不存在,就不會調用完成的處理程序?或者也許它掛在處理程序的內存地址,所以它不會被垃圾收集?所以你所要做的就是添加一個Completed事件處理程序,發起你的調用servicenameAsync(),並等待它返回你的處理程序(在UI線程上)。我也只是爲了確保將完成的處理程序包裝在try/catch塊中以處理ObjectDisposedExceptions(如果有的話)。

現在的主要問題是:它不會破壞。

一個問題(至少對我來說)...我使用單例模式來訪問我的WCF服務。與此問題是,它創建一個靜態引用到您的WCF代理,用於整個應用程序。聽起來很方便,但是當您在進行異步調用之前將事件處理程序追加到Completed事件中時,則可能會出現重複,重複等情況。每次撥打電話時,都會添加另一個完成的事件處理程序已經添加到您的靜態WCF代理。爲了解決這個問題,我開始爲每個調用聲明一個新的代理客戶端,或者如果每個winform沒有爲每個winform類創建多個調用。如果有人對此或更好的方式有任何意見,請讓我知道!單例模式的最大問題是,如果您有多個同時打開多個窗口(不同類)的窗口調用相同的異步方法,但您希望它們返回到不同的完成處理程序,則無法這樣做。

+1

關於答案的最後部分(關於使用同一個代理實例的多個窗口),您可以在調用異步方法時傳遞一個userState對象,該對象將在相應的Completed事件的EventArgs中返回。 此userState對象可用於區分調用同一方法的多個窗口,並確定哪些結果對應於特定的窗口實例。 – 2010-07-22 14:32:00

+0

優秀的評論。主要問題是,我需要調用2個不同的完成處理程序。一次調用應該返回到完成的處理程序#1,另一個表單的另一個調用需要調用完成的處理程序#2。我看不到一個簡單的方法來做到這一點。我將不得不設置總是被調用的某種類型的全局事件處理程序,然後使用userstate對象來確定究竟需要完成什麼。 – 2010-07-27 18:56:18

0

當操作完成時,您正在給回調一個地址。該地址在關閉表單時無效,因此您收到此錯誤。您需要建立一種確定您的表單是否有未決通話的方式,然後才允許您的表單關閉。

我想研究BackgroundWorker class。我相信你仍然可以通過在回調觸發之前關閉表單來設置崩潰,但通過BackgroundWorker,你應該能夠詢問它的狀態並更好地處理你的情況。

在Async調用仍處於活動狀態時,您不應讓表單關閉。使用BackgroundWorker,您可以輕鬆確定異步呼叫是否處於活動狀態。

+0

有關使用try/catch塊爲ObjectDisposedException封裝Invoke調用並拋出異常的想法? – 2010-06-14 20:11:32

+0

Try-catch無濟於事。當呼叫完成時,異步呼叫具有對GONE(表單已關閉)的內存地址的引用。看看這篇關於在Winforms中使用BackgroundWorker的文章(注意如何取消):http://bit.ly/cBQUhF – 2010-06-14 20:26:31

+0

你是說我應該使用後臺工作者在我的應用程序中完成所有的異步wcf調用?這是一個很好的模式嗎? – 2010-07-20 17:24:09

1
public void UpdateUserPhoto(int userID, Binary photo) 
{ 
    if (Disposed || !IsHandleCreated) 
    return; 
    if (InvokeRequired) 
    ... 
+0

如果垃圾收集已經發生在窗體上怎麼辦? – 2010-06-14 20:52:07

+3

我認爲這不會發生。由於WCF回調仍然保留對錶單的引用,我認爲這足以阻止它被收集。 – 2010-06-14 21:26:54

0

我相信在Form對象上有一個IsDisposed屬性。您可以在調用Invoke之前檢查該屬性。

+0

試過...調試器說IsDisposed = false,但仍然崩潰說對象被處置。 – 2010-07-02 12:56:10

+0

嗯,我剛剛發現這個問題(http://stackoverflow.com/questions/1874728/avoid-calling-invoke-when-the-control-is-disposed)。似乎它與你的問題有關。 – 2010-07-02 14:54:38

0

創建一個標誌,例如「IsWaiting」作爲ManualResetEvent(甚至是簡單的bool),將其設置爲true,並且只在異步結果返回時將其設置爲false。

在你的類dispose方法中檢查了標誌,並且只有標誌清除後才處理對象。 (爲了防止出現錯誤,請暫時停用)