Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Notify when thread is complete, without locking calling thread

I am working on a legacy application that is built on top of NET 3.5. This is a constraint that I can't change. I need to execute a second thread to run a long running task without locking the UI. When the thread is complete, somehow I need to execute a Callback.

Right now I tried this pseudo-code:

Thread _thread = new Thread(myLongRunningTask) { IsBackground = True };
_tread.Start();
// wait until it's done
_thread.Join();
// execute finalizer

The second option, which does not lock the UI, is the following:

Thread _thread = new Thread(myLongRunningTask) { IsBackground = True };
_tread.Start();
// wait until it's done
while(_thread.IsAlive)
{
    Application.DoEvents();
    Thread.Sleep(100);
}
// execute finalizer

Of course the second solution is not good cause it overcharge the UI. What is the correct way to execute a callback when a _thread is complete? Also, how do I know if the thread was cancelled or aborted?

*Note: * I can't use the BackgroundWorker and I can't use the Async library, I need to work with the native thread class.

like image 818
Raffaeu Avatar asked Sep 27 '13 10:09

Raffaeu


3 Answers

There are two slightly different kinds of requirement here:

  • Execute a callback once the long-running task has completed
  • Execute a callback once the thread in which the long-running task was running has completed.

If you're happy with the first of these, the simplest approach is to create a compound task of "the original long-running task, and the callback", basically. You can even do this just using the way that multicast delegates work:

ThreadStart starter = myLongRunningTask;
starter += () => {
    // Do what you want in the callback
};
Thread thread = new Thread(starter) { IsBackground = true };
thread.Start();

That's very vanilla, and the callback won't be fired if the thread is aborted or throws an exception. You could wrap it up in a class with either multiple callbacks, or a callback which specifies the status (aborted, threw an exception etc) and handles that by wrapping the original delegate, calling it in a method with a try/catch block and executing the callback appropriately.

Unless you take any special action, the callback will be executed in the background thread, so you'll need to use Control.BeginInvoke (or whatever) to marshal back to the UI thread.

like image 119
Jon Skeet Avatar answered Nov 11 '22 16:11

Jon Skeet


I absolutely understand your requirements, but you've missed one crucial thing: do you really need to wait for the end of that thread synchronously? Or maybe you just need to execute the "finalizer" after thread's end is detected?

In the latter case, simply wrap the call to myLongRunningTask into another method:

void surrogateThreadRoutine() {
    // try{ ..

    mytask();

    // finally { ..
    ..all 'finalization'.. or i.e. raising some Event that you'll handle elsewhere
}

and use it as the thread's routine. That way, you'll know that the finalization will occur at the thread's and, just after the end of the actual job.

However, of course, if you're with some UI or other schedulers, the "finalization" will now run on yours thread, not on the "normal threads" of your UI or comms framework. You will need to ensure that all resources are external to your thread-task are properly guarded or synchronized, or else you'll probably clash with other application threads.

For instance, in WinForms, before you touch any UI things from the finalizer, you will need the Control.InvokeRequired (surely=true) and Control.BeginInvoke/Invoke to bounce the context back to the UI thread.

For instance, in WPF, before you touch any UI things from the finalizer, you will need the Dispatcher.BeginInvoke..

Or, if the clash could occur with any threads you control, simple proper lock() could be enough. etc.

like image 1
quetzalcoatl Avatar answered Nov 11 '22 16:11

quetzalcoatl


You can use a combination of custom event and the use of BeginInvoke:

public event EventHandler MyLongRunningTaskEvent;

private void StartMyLongRunningTask() {
    MyLongRunningTaskEvent += myLongRunningTaskIsDone;
    Thread _thread = new Thread(myLongRunningTask) { IsBackground = true };
    _thread.Start();
    label.Text = "Running...";
}

private void myLongRunningTaskIsDone(object sender, EventArgs arg)
{
    label.Text = "Done!";
}

private void myLongRunningTask()
{
    try 
    { 
        // Do my long task...
    } 
    finally
    {
        this.BeginInvoke(Foo, this, EventArgs.Empty);
    }
}

I checked, it's work under .NET 3.5

like image 1
AxFab Avatar answered Nov 11 '22 16:11

AxFab