views:

362

answers:

3

I have a UserControl with something that does some background work. When thing has a completed event, which the user control is subscribed to. Since the event is raised in a separate thread and the event handler does some updating of the user control I start the event handler with this:

private void ProcessProcessCompleted(object sender, ProcessCompletedEventArgs e)
{
    if (InvokeRequired)
    {
        Invoke(new Action<object, ProcessCompletedEventArgs>(ProcessProcessCompleted), 
            new[] { sender, e });
        return;
    }

    // Update the user control, etc.
}

This works fine, except if the form that contains the user control is closed before the process has completed. I tried to unsubscribe to the event in the dispose method of the user control, but that only seems to work parts of the time. How should I be doing this? Would like to avoid wrapping it in a try catch block. Is there an InvokeIsPossible property or something I can check before I Invoke?

+2  A: 

You could use the SynchronizationContext, it was introduced in .NET 2 to simplify inter-thread communication:

  • create a global SynchronizationContext variable (let's say syncContext)
  • initialize it to the form's thread sync'context:

    syncContext = SynchronizationContext.Current;
    

change your ProcessProcessCompleted method like this:

    private void ProcessProcessCompleted(object sender, object e)
    {
        this.syncContext.Post(new SendOrPostCallback(delegate(object state)
                                                    {
                                                        // Update the user control, etc.
                                                    })
                              , null);            
    }

you can read about SynchronizationContext here:

http://www.codeproject.com/KB/cpp/SyncContextTutorial.aspx

or http://msdn.microsoft.com/fr-fr/library/system.threading.synchronizationcontext.aspx

najmeddine
+1 for linking to the french MSDN alone.
Henk Holterman
I was excited, didn't pay attention, I'll leave it to justify my +1 ;)
najmeddine
English version here: http://msdn.microsoft.com/en-gb/library/system.threading.synchronizationcontext.aspx
Lucas Jones
Would you do this in the form or in the class that does the background work?
Svish
this.syncContext.Post replace the InvokeRequired..Invoke pattern. You just have to save a reference to the SynchronizationContext of the main thread (in the form constructor for example) and give any code you want to be executed in the main thread to the Post function of that SynchronizationContext.
najmeddine
+1  A: 

Although it doesn't synchronize the threads, you can use Control.IsDisposed to check whether the Invoke() method can be called in the current state (Form inherits from Control):

if (!this.IsDisposed)
{
   // tatata...
}

To guarantee that is works, you must wrap it in a lock(syncobject) { ... } block to check a custom boolean flag and likewise change that flag in a lock-statement inside a Form_Close event.

Cecil Has a Name
+1. I've used this technique in the past and it works.
Rui Craveiro
I tried this, and sometimes it still failed. No idea why though...
Svish
Did you check the state of `IsDisposed` in the debugger when it failed?
Cecil Has a Name
A: 

I use a class I call a thread-gate which I usually inherit from but you can just instantiate it or whatever. This uses the SynchronizationContext class.

public class ThreadGate
{
    private readonly SynchronizationContext _synchronizationContext;
    public ThreadGate()
    {
        _synchronizationContext = AsyncOperationManager.SynchronizationContext;
    }

    public void Post<T>(Action<T> raiseEventMethod, T e)
    {
        if (_synchronizationContext == null)
            ThreadPool.QueueUserWorkItem(delegate { raiseEventMethod(e); });
        else
            _synchronizationContext.Post(delegate { raiseEventMethod(e); }, null);
    }
}

I use it like this:

Post(OnProgressChanged,new ProgressChangedEventArgs(1,null)));

Or if you like Action

Action<string> callback;
Post(callback,str));
Lee Treveil
Interesting solution. Is this ThreadGate used in the gui, or by the class that does the background work?
Svish
both, technically but I use it in the class that does the background work then I never have to worry the invoke required pattern. The answer I posted only really becomes necessary when there are many events. But it can be used on a single event obviously.
Lee Treveil