2009-12-09 35 views
23

我在我的工作線程以下代碼(下面ImageListViewControl派生):避免調用調用當控制被設置

if (mImageListView != null && 
    mImageListView.IsHandleCreated && 
    !mImageListView.IsDisposed) 
{ 
    if (mImageListView.InvokeRequired) 
     mImageListView.Invoke(
      new RefreshDelegateInternal(mImageListView.RefreshInternal)); 
    else 
     mImageListView.RefreshInternal(); 
} 

然而,我得到一個ObjectDisposedException有時與上述Invoke方法。看來,控制可以在我檢查IsDisposed和我打電話Invoke之間處理。我怎樣才能避免這種情況?

+0

爲什麼它處在第一位? – RvdK 2009-12-09 15:51:32

+0

@PoweRoy:我發信號線程退出控件的Dispose方法。我知道這不是最好的做法,但我找不到一個更好的地方來指示線程退出。 – 2009-12-09 16:06:58

回答

13

您的代碼中存在隱式競態條件。該控件可以放置在您的IsDisposed測試和InvokeRequired測試之間。 InvokeRequired和Invoke()之間還有一個。如果不確保控制超出線程壽命,則無法解決此問題。鑑於你的線程正在爲列表視圖生成數據,它應該在列表視圖消失之前停止運行。

這樣做是通過在FormClosing事件中設置e.Cancel並通過ManualResetEvent指示線程停止。線程完成後,再次調用Form.Close()。 Thread.Abort()是一個遙遠的第二選擇,但更容易實現。使用BackgroundWorker可以輕鬆實現線程完成邏輯。

+0

不像Isak Savo那樣捕獲ObjectDisposedException異常,它比以下更清晰修補惡意的Thread.Abort()或依靠FormClosing事件? – arul 2009-12-09 16:04:31

+2

你能保證它只會是一個ObjectDisposedException嗎?如果它是一個合法的ObjectDisposedException呢?這是一個兔子洞。解決問題,不要拍攝信使。 – 2009-12-09 16:16:02

+0

我最終做了類似的事情。但不是處理FormClosing,而是等待線程退出時,我將覆蓋控件的OnHandleDestroyed並阻止UI線程。 – 2009-12-11 14:58:58

0

的一種方式可能是更多而不是調用調用ImageListView法的方法本身的:

if (mImageListView != null && 
    mImageListView.IsHandleCreated && 
    !mImageListView.IsDisposed) 
{ 
    if (mImageListView.InvokeRequired) 
     mImageListView.Invoke(new YourDelegate(thisMethod)); 
    else 
     mImageListView.RefreshInternal(); 
} 

這樣,它終於在調用RefreshInternal前會檢查一次()。

1

可能是鎖定(mImageListView){...}?

+0

這不起作用。不能保證Disposed在鎖內時不會被調用。 – 2009-12-09 15:48:34

+0

+1表示我要說的話。 – Jrud 2009-12-09 15:49:02

+0

@Isak鎖的目的是阻止其他線程訪問對象。如果他鎖定物體,那麼按照定義,鎖定時不能處置。 – Jrud 2009-12-09 15:50:35

16

你在這裏有一個race condition。你最好只是捕捉ObjectDisposed異常並完成它。實際上,我認爲在這種情況下它是只有工作解決方案。

try 
{ 
    if (mImageListView.InvokeRequired) 
     mImageListView.Invoke(new YourDelegate(thisMethod)); 
    else 
     mImageListView.RefreshInternal(); 
} 
catch (ObjectDisposedException ex) 
{ 
    // Do something clever 
} 
+0

我真的希望避免嘗試/追趕。但如果這是唯一的解決方案,我會這麼做。 – 2009-12-09 15:50:42

+0

那麼你可以*通過使用互斥鎖或鎖來解決它,但它更容易出錯,並且隨着代碼的發展可能會導致怪異的錯誤。您需要使用相同的互斥鎖來保護對Dispose()的所有調用,隨着代碼的發展,這將變得更加困難...... – 2009-12-09 15:59:57

1

您可以使用互斥鎖。

某處在螺紋的開始:

Mutex m=new Mutex(); 

然後:

if (mImageListView != null && 
    mImageListView.IsHandleCreated && 
    !mImageListView.IsDisposed) 
{ 
    m.WaitOne(); 

    if (mImageListView.InvokeRequired) 
     mImageListView.Invoke(
      new RefreshDelegateInternal(mImageListView.RefreshInternal)); 
    else 
     mImageListView.RefreshInternal(); 

    m.ReleaseMutex(); 
} 

而徘徊無論是你要丟棄mImageListView的:

m.WaitOne(); 
mImageListView.Dispose(); 
m.ReleaseMutex(); 

這應該確保你不能同時處置和調用。

+0

IsDisposed檢查和WaitOne調用之間還沒有競爭狀態嗎? – 2009-12-11 14:55:22

+0

沒錯,你是對的。您可以在WaitOne調用之後再次檢查IsDisposed,或者可以在IsDisposed檢查之前放置WaitOne。如果您預期在執行代碼以保存額外的呼叫時,您預期IsDisposed的錯誤次數超過了true,我會選擇後一個選項。 – 2009-12-11 15:16:04

1

也看到這個問題:

Avoiding the woes of Invoke/BeginInvoke in cross-thread WinForm event handling?

是導致EventHandlerForControl可以解決這個問題的事件方法簽名的實用工具類。你可以調整這個類或者回顧其中的邏輯來解決這個問題。

這裏真正的問題是,nobugz是正確的,因爲他指出在winform中爲跨線程調用提供的API本質上不是線程安全的。即使在調用InvokeRequired和Invoke/BeginInvoke本身的過程中,也有幾種競爭條件會導致意外的行爲。

1

如果BackgroundWorker的是一種可能性,有規避這是一個非常simple方式:

public partial class MyForm : Form 
{ 
    private void InvokeViaBgw(Action action) 
    { 
     BGW.ReportProgress(0, action); 
    } 

    private void BGW_ProgressChanged(object sender, ProgressChangedEventArgs e) 
    { 
     if (this.IsDisposed) return; //You are on the UI thread now, so no race condition 

     var action = (Action)e.UserState; 
     action(); 
    } 

    private private void BGW_DoWork(object sender, DoWorkEventArgs e) 
    { 
     //Sample usage: 
     this.InvokeViaBgw(() => MyTextBox.Text = "Foo"); 
    } 
} 
1

處理表單關閉事件。檢查你的UI線程工作是否仍在進行,如果這樣,開始放下它,取消關閉事件,然後使用表單控件上的BeginInvoke重新調度關閉。

private void Form_FormClosing(object sender, FormClosingEventArgs e) 
{ 
    if (service.IsRunning) 
    { 
     service.Exit(); 
     e.Cancel = true; 
     this.BeginInvoke(new Action(() => { this.Close(); })); 
    } 
} 
1

由伊薩克薩沃提出的解決方案

try 
    { 
    myForm.Invoke(myForm.myDelegate, new Object[] { message }); 
    } 
catch (ObjectDisposedException) 
    { //catch exception if the owner window is already closed 
    } 

工作在C#4.0,但由於某種原因,無法在C#3.0(該異常反正升起)

所以我用另一種解決方案基於指示表單是否關閉的標誌並且因此如果標誌被設置則阻止使用調用

public partial class Form1 : Form 
    { 
    bool _closing; 
    public bool closing { get { return _closing; } } 

    private void Form1_FormClosing(object sender, FormClosingEventArgs e) 
    { 
     _closing = true; 
    } 

... 

// part executing in another thread: 

if (_owner.closing == false) 
    { // the invoke is skipped if the form is closing 
    myForm.Invoke(myForm.myDelegate, new Object[] { message }); 
    } 

這具有完全避免使用try/catch的優點。

+0

我認爲添加和刪除eventhandlers並檢查isDisposed是最常用的方式,但我得說。這對我來說非常合適。擺脫try-catch,不必使用事件,所有的費用都是一個額外的布爾:-)謝謝你的提示! – 2017-03-16 12:20:38

3

使用try

if(!myControl.Disposing) 
    ; // invoke here 

我有完全相同的問題,因爲你。自從我開始檢查控件時,ObjectDisposedException已經消失。不是說這會在100%的時間內完成修復,只有99%;)檢查到Disposing和調用調用之間仍然存在競爭條件的可能性,但是在測試中我已經完成了(我使用ThreadPool和一個工作線程)。

這裏是我每次調用前使用調用:

private bool IsControlValid(Control myControl) 
    { 
     if (myControl == null) return false; 
     if (myControl.IsDisposed) return false; 
     if (myControl.Disposing) return false; 
     if (!myControl.IsHandleCreated) return false; 
     if (AbortThread) return false; // the signal to the thread to stop processing 
     return true; 
    } 
+0

+1來檢查IsHandleCreated是否爲真/假。這並不能解決比賽條件,但需要牢記。 – TamusJRoyce 2013-07-16 13:38:32

0

的建議,停止線程產生的信息是不能接受的。代表可以組播。因爲一個聽衆不想聽樂隊,所以你不要拍樂隊成員。 由於框架沒有提供任何簡單的方法,我知道要清除這些事件消息的消息泵,並且由於表單並未公開其讓我們知道表單正在關閉的私有屬性: 在IsClosing上設置標誌取消訂閱或停止收聽事件後,窗口事件,並且在執行this.Invoke()之前始終檢查此標誌。

2

現實情況是,對於Invoke和朋友,您無法完全防止對已處置組件的調用,然後由於缺少句柄而導致InvalidOperationException。我還沒有真正看到過答案,就像下面的答案一樣,在任何解決先前測試或使用鎖定語義無法完全解決的問題性問題的線程中。

這裏是正常的 '正確' 的成語:

// the event handler. in this case preped for cross thread calls 
void OnEventMyUpdate(object sender, MyUpdateEventArgs e) 
{ 
    if (!this.IsHandleCreated) return; // ignore events if we arn't ready, and for 
             // invoke if cant listen to msg queue anyway 
    if (InvokeRequired) 
     Invoke(new MyUpdateCallback(this.MyUpdate), e.MyData); 
    else 
     this.MyUpdate(e.MyData); 
} 

// the update function 
void MyUpdate(Object myData) 
{ 
    ... 
} 

的fundemental問題:

在使用Invoke設施中使用的窗口消息隊列,這在放置一個消息排隊等待,或者像發佈或發送消息一樣完全消除交叉線程調用。如果在Invoke消息之前有一條消息會使組件及其窗口句柄無效,或者在您嘗試執行任何檢查之後放置該消息,那麼您將會遇到不好的時間。

x thread -> PostMessage(WM_CLOSE); // put 'WM_CLOSE' in queue 
y thread -> this.IsHandleCreated  // yes we have a valid handle 
y thread -> this.Invoke();   // put 'Invoke' in queue 
ui thread -> this.Destroy();   // Close processed, handle gone 
y thread -> throw Invalid....()  // 'Send' comes back, thrown on calling thread y 

有知道該控件是要去掉自己fromthe隊列中沒有真正的方法,並沒有什麼合理的可以做,以「撤銷」的調用。不管你做了多少次檢查或你製造了額外的鎖,你都不能阻止其他人發佈類似關閉的東西,或停用。有很多senarios會發生這種情況。

A液:

首先要知道的是,調用是要失敗的不是如何(IsHandleCreated)檢查將忽略該事件,沒有什麼不同。如果目標是保護非UI線程上的調用者,則需要處理該異常,並將其視爲任何其他未成功的調用(以防止應用程序崩潰或執行任何操作),除非要重寫/重擲調用設施,美中不足的是你知道的唯一途徑。

// the event handler. in this case preped for cross thread calls 
void OnEventMyWhatever(object sender, MyUpdateEventArgs e) 
{ 
    if (!this.IsHandleCreated) return; 
    if (InvokeRequired) 
    { 
     try 
     { 
      Invoke(new MyUpdateCallback(this.MyUpdate), e.MyData); 
     } 
     catch (InvalidOperationException ex) // pump died before we were processed 
     { 
      if (this.IsHandleCreated) throw; // not the droids we are looking for 
     } 
    } 
    else 
    { 
     this.MyUpdate(e.MyData); 
    } 
} 

// the update function 
void MyUpdate(Object myData) 
{ 
    ... 
} 

異常過濾可以定製,以滿足任何的需求是什麼。其良好的知道工作線程往往不具備所有輕鬆的外異常處理並在大多數應用程序中記錄UI線程,因此您可能希望只是在工作端吞噬任何異常,或者記錄並重新拋出所有這些異常。對於很多工作線程中未捕獲的異常,意味着應用程序將崩潰。

+1

我真的希望有'TryInvoke'和'TryBeginInvoke'方法來處理對象正在嘗試執行某種操作的情況,因爲它的可見部分需要更新以反映新數據。期望的後置條件是控件沒有任何部分仍然需要更新。控制的處理意味着沒有可見的部分,因此沒有過時的可見部分;因此,後續條件將得到滿足,並且該方法應該成功返回。 – supercat 2013-09-06 16:02:12

0

我有同樣的錯誤。我的錯誤發生在線程中。最後我寫這個方法:

public bool IsDisposed(Control ctrl) 
{ 
    if (ctrl.IsDisposed) 
     return true; 
    try 
    { 
     ctrl.Invoke(new Action(() => { })); 
     return false; 
    } 
    catch (ObjectDisposedException) 
    { 
     return true; 
    } 
}