Avoid invoking Invoke when deleting a control

I have the following code in my workflow ( ImageListView below is derived from Control ):

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

However, sometimes I get an ObjectDisposedException using the Invoke method above. It looks like control can be set between checking IsDisposed time, and I call Invoke . How can i avoid this?

+23
c # invoke controls dispose
Dec 09 '09 at 15:39
source share
12 answers

Your code has implicit race conditions. The control can be removed between your IsDisposed test and the InvokeRequired test. There's another one between InvokeRequired and Invoke (). You cannot fix this without controlling the life of the stream. Given that your stream generates data to represent the list, it should stop working until the list disappears.

Do this by setting e.Cancel in the FormClosing event and telling the thread to stop using ManualResetEvent. When the thread completes, call Form.Close () again. Thread.Abort () is the second choice, but much easier to implement. Using BackgroundWorker simplifies the implementation of thread completion logic.

+13
Dec 09 '09 at 15:52
source share

You have a race condition here. You better just catch the ObjectDisposed exception and make one with it. In fact, I think in this case this is the only working solution.

 try { if (mImageListView.InvokeRequired) mImageListView.Invoke(new YourDelegate(thisMethod)); else mImageListView.RefreshInternal(); } catch (ObjectDisposedException ex) { // Do something clever } 
+16
Dec 09 '09 at 15:47
source share

Try using

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

I had the same problem as you. Since I switched to verification. Disposal of ObjectDisposedException removed. Without saying that it will fix it in 100% of cases, only 99%;) There is still a chance for the race status between the Disposing check and the challenge to call, but in the test I did, I did not run (I use ThreadPool and the workflow )

Here is what I use before each call to call:

  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; } 
+3
Oct 30 '12 at 16:44
source share

The reality is that with Invoke and friends you cannot fully protect against invoke on the remote component and then get an InvalidOperationException due to the lack of a descriptor. I havnt really saw the answer yet, like the one below, in any of the threads, which touches on a fundamental problem that cannot be completely solved by selective testing or using lock semantics.

Here's the normal "right" idiom:

 // 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) { ... } 



Fundamental problem:

When using the Invoke tool, the Windows message queue is used, which puts the message in the queue to either wait or fire and forget about the cross-flow, just like a Mail or Send message. If a message appears before the Invoke message that will invalidate the component and its window handle, or will be placed immediately after any checks that you are trying to perform, then you will have a bad time.

  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 

There is no real way to find out that the control is about to remove itself from the queue, and you cannot do anything reasonable to β€œcancel” the call. No matter how many checks you make or what additional locks you make, you cannot stop someone else by issuing something like closing or deactivating. There are tons of senaria where this can happen.

Decision:

The first thing to understand is that the call will fail, no different from how the check (IsHandleCreated) would ignore the event. If the goal is to protect the caller from a thread other than the UI, you will need to handle the exception and treat it like any other call that fails (so that the application does not hang or does anything. And if you don't rewrite / reeroll Invoke Your catch is the only way to find out.

 // 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) { ... } 

Exception filtering can be tailored to suit your needs. It’s good to know that workflows often do not have all the soft external handling of exceptions and registration of user interface threads in most applications, so you can simply gobble up any exception on the worker side. Or record and flip all of them. For many exceptions thrown from the workflow, it means the application will crash.

+2
Sep 05 '13 at 22:41
source share

can be blocked (mImageListView) {...}?

+1
Dec 09 '09 at 15:45
source share

You can use mutexes.

Somewhere at the beginning of the stream:

  Mutex m=new Mutex(); 

Then:

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

And if you use the mImageListView utility:

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

This should ensure that you cannot delete and call at the same time.

+1
Dec 09 '09 at 15:55
source share

See also this question:

Avoid Invoke / BeginInvoke problems in handling WinForm events with multiple threads?

The utility class that EventHandlerForControl cited can solve this problem for event method signatures. You can adapt this class or revise the logic to solve the problem.

The real problem here is that nobugz is right, as it indicates that the APIs provided for cross-thread calls in winforms are inherently not thread safe. Even inside InvokeRequired and Invoke / BeginInvoke calls, there are several race conditions that can cause unexpected behavior.

+1
Dec 09 '09 at 17:04
source share

If BackGroundWorker is an opportunity, there is a very easy way, simple to get around this:

 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
Nov 15 '10 at 23:55
source share

Handle the form closing event. Check if your work with user interfaces is working, if so, to start canceling it, cancel the close event and then transfer the completion using BeginInvoke in the form control.

 private void Form_FormClosing(object sender, FormClosingEventArgs e) { if (service.IsRunning) { service.Exit(); e.Cancel = true; this.BeginInvoke(new Action(() => { this.Close(); })); } } 
+1
Sep 06 2018-11-11T00:
source share

Solution proposed by Isak Savo

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

works in C # 4.0, but for some reason it fails in C # 3.0 (an exception is thrown anyway)

So, I used a different solution based on a flag indicating whether the form closes and therefore prevents the call from being used if the flag is set

  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 }); } 

This has the advantage of completely avoiding the use of try / catch.

+1
Oct. 16 '11 at 12:17
source share

One way might be to call the method itself, rather than calling the ImageListView method:

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

That way, it will check again before finally calling RefreshInternal ().

0
Dec 09 '09 at 15:43
source share

The suggestion to stop the thread generating messages is not acceptable. Delegates may be multicast. Since one listener does not want to listen to the group, you are not taking pictures of the group members. Since the structure does not provide an easy way, I know to clear the message pump of these event messages, and since the form does not disclose its private property, which allows us to know that the form is closing: Set the flag in the IsClosing event of the window after you cancel subscribe or stop listening to events, and always check this flag before doing this. Invoke ().

0
Jun 28 '13 at 15:56
source share



All Articles