How to dispose of the form correctly, without the risk of invoking Invoke from another thread on the remote object?

I have a form that "listens" for events that occur elsewhere (not on the Form itself, not on any of its child controls). Events are created by objects that exist even after the Form is placed, and can occur in streams other than those in which the form handle was created, which means that I need to execute Invoke in the event handler (to show a change to the form, for example).

In the Dispose(bool) method of the (overridden Dispose(bool) form, I unsubscribed from all events that can still be signed when this method is called. However, Invoke is still sometimes called from one of the event handlers. I assume that this is because the event handler is called just a minute before the event is canceled, and then the OS will switch to the control method that executes and then return control to the handler that calls the Invoke method on the remote object.

Blocking threads does not help, because invoke calls block the calling thread until the main thread processes the called method. This may never happen, because the main thread itself can wait to release the lock on the object that was called by the thread of the calling call, thereby creating a deadlock.

So, in short, how do I properly manage a form when it subscribes to external events that may occur in different threads?

Here are some key methods. This approach suffers from the problems described above, but I'm not sure how to fix them.

This is an event handler that handles a change in a piece of model data:

 private void updateData() { if (model != null && model.Data != null) { model.Data.SomeDataChanged -= new MyEventHandler(updateSomeData); model.Data.SomeDataChanged += new MyEventHandler(updateSomeData); } updateSomeData(); } 

This is an event handler that should make changes to the view:

 private void updateSomeData() { if (this.InvokeRequired) this.myInvoke(new MethodInvoker(updateSomeData)); else { // do the necessary changes } } 

And the myInvoke method:

 private object myInvoke(Delegate method) { object res = null; lock (lockObject) { if (!this.IsDisposed) res = this.Invoke(method); } return res; } 

My redefinition of the Dispose(bool) method:

 protected override void Dispose(bool disposing) { lock (lockObject) { if (disposing) { if (model != null) { if (model.Data != null) { model.Data.SomeDataChanged -= new MyEventHandler(updateSomeData); } // unsubscribe other events, omitted for brevity } if (components != null) { components.Dispose(); } } base.Dispose(disposing); } } 

Update (as requested by Alan):

I never explicitly call the Dispose method, I assume that this is done using the framework. The deadlock so far has only been when the application is closed. Before I made the lock, I sometimes got some exceptions that were thrown when the form was just closed.

+4
source share
3 answers

There are two approaches to consider. One of them is to have a lock object inside Form , and internal calls to Dispose and BeginInvoke occur inside the lock; since neither Dispose nor BeginInvoke should last long, the code should not wait long for the lock.

Another approach is to simply state that due to design errors in Control.BeginInvoke / Form.BeginInvoke these methods sometimes raise an exception that cannot be practically prevented, and it is easy to catch it in cases where it does not really matter whether the action takes place in a form that was located in any case.
+3
source

I would like to provide a peculiar addition to the supercat answer, which may be interesting.

Start by creating a CountdownEvent (we'll call it _invoke_counter) with an initial number of 1. This should be a member variable of the form itself (or control):

 private readonly CountdownEvent _invoke_counter = new CountdownEvent(1); 

Wrap each use of Invoke / BeginInvoke as follows:

 if(_invoke_counter.TryAddCount()) { try { //code using Invoke/BeginInvoke goes here } finally { _invoke_counter.Signal(); } } 

Then at your disposal Dispose you can:

 _invoke_counter.Signal(); _invoke_counter.Wait(); 

It also allows you to do a few other nice things. The CountdownEvent.Wait () function has an overload with a timeout. Perhaps you only need to wait a certain period of time to allow the calling functions to finish before allowing them to die. You can also do something like Wait (100) in a loop with DoEvents () to keep it responsive if you expect Invokes to take a lot of time. Thanks to this method, you can find a lot of superiority.

This should prevent any problems associated with the strange expiration date, and it is quite simple to understand and implement. If anyone sees any serious problems with this, I would like to hear about them because I use this method in production software.

IMPORTANT: make sure the delete code is on the Finalizer thread (which should be "natural"). If you try to manually call the Dispose () method from the UI thread, it will be blocked because it gets stuck on _invoke_counter.Wait (); and Invokes will not start, etc.

0
source

I had a problem with the Invoke method with multithreading, and I found a solution that works like a charm!

I wanted to create a loop in a task that updates the label on the monitoring form.

But when I closed the form window, my Invoke threw an exception because my form is located!

Here is the template I implemented to solve this problem:

 class yourClass : Form { private bool isDisposed = false; private CancellationTokenSource cts; private bool stopTaskSignal = false; public yourClass() { InitializeComponent(); this.FormClosing += (s, a) => { cts.Cancel(); isDisposed = true; if (!stopTaskSignal) a.Cancel = true; }; } private void yourClass_Load(object sender, EventArgs e) { cts = new CancellationTokenSource(); CancellationToken token = cts.Token; Task.Factory.StartNew(() => { try { while (true) { if (token.IsCancellationRequested) { token.ThrowIfCancellationRequested(); } if (this.InvokeRequired) { this.Invoke((MethodInvoker)delegate { methodToInvoke(); }); } } } catch (OperationCanceledException ex) { this.Invoke((MethodInvoker)delegate { stopTaskSignalAndDispose(); }); } }, token); } public void stopTaskSignalAndDispose() { stopTaskSignal = true; this.Dispose(); } public void methodToInvoke() { if (isDisposed) return; label_in_form.Text = "text"; } } 

I execute the ToInvoke () method in a call to update the label from the form stream.

When I close the window, the FormClosing event is fired. I take this opportunity to cancel closing a window (a.Cancel) and call the Cancel method of the Task object to stop the thread.

Then I turn to the ThrowIfCancellationRequested () method, which throws an OperationCanceledException, which allows, juste after, to exit the loop and complete the task.

The Invoke method sends a window message to the queue.

Microsoft says: "For each thread that creates a window, the operating system creates a queue for window messages."

So I call another method that now really closes the window, but this time using the Invoke method to make sure this message is the last of the queue!

And then I close the window using the Dispose () method.

0
source

All Articles