Search code examples
c#multithreadingeventscancellation

Waiting for an event-raising thread to cancel


I have a worker thread that reports progress by raising an event.

private volatile bool canRun;

public void Run()
{
    for (int i = 0; i < count && canRun; i++)
    {
        // do stuff

        OnProgressMade((float)i / (float)count);
    }

    OnWorkFinished();
}

public void Cancel()
{
    canRun = false;

    // wait for the thread to finish
}

In the Cancel method, I would like to wait for the thread to finish. Otherwise, someone might call Cancel and then immediately Dispose while the thread is still "doing stuff". If I used Join, there might be a deadlock - UI waits for the thread to cancel, thread waits for the UI to handle the ProgressMade event.

Is there a way to solve this nicely, or is it bad design from the beginning? My current solution relies on the UI waiting for the WorkFinished event before disposing the worker.


Solution

  • In the comments you hint that you might be able to use await (which is possible even on .NET 4.0). It could look like this:

    MyWorker w;
    CancellationTokenSource cts;
    
    void OnStart(eventargs bla bla) {
     cts = new ...();
     w = new ...(cts.Token);
    }
    
    void OnCancel(eventargs bla bla) {
     cts.Cancel();
     await w.WaitForShutdown();
     MsgBox("cancelled");
    }
    

    And we need to make MyWorker cooperate:

    class MyWorker {
    CancellationToken ct = <from ctor>;
    TaskCompletionSource<object> shutdownCompletedTcs = new ...();
    
    public void Run()
    {
        for (int i = 0; i < count && !ct.IsCancellationRequested; i++)
        {
            // do stuff
    
            OnProgressMade((float)i / (float)count);
        }
    
        //OnWorkFinished(); //old
        shutdownCompletedTcs.SetResult(null); //new, set "event"
    }
    
    public Task WaitForShutdown() {
     return shutdownCompletedTcs.Task;
    }
    }
    

    (Quickly slapped together.) Note, that all wait operations use await. They release the UI thread without interrupting your control flow.

    Also note, that MyWorker is cooperatively cancellable. It does not know anything about the UI.