Search code examples
c#multithreadingcancellation

How to poll from worker thread for cancellation


I have a UI which spawns off a background worker thread which performs a complex tree of tasks and sub-tasks that takes about a minute to complete.

A requirement is that the background worker task must be capable of being cancelled once it has begun.

At the moment my solution is naive and makes the code a mess. When a cancel button is pressed in the UI, a cancel token is set. The worker thread periodically (between tasks) polls this token and if it is set, it exits:

void ThreadWorkerHandler(CancelToken cancelToken)
{
    DoTask1(cancelToken);
    if (cancelToken.IsSet)
        return;
    DoTask2(cancelToken);
    if (cancelToken.IsSet)
        return;
    DoTask3(cancelToken);
    if (cancelToken.IsSet)
        return;
    DoTask4(cancelToken);
}

void DoTask2(CancelToken cancelToken)
{
    DoSubTask2a();
    if (cancelToken.IsSet)
        return;
    DoSubTask2b();
    if (cancelToken.IsSet)
        return;
    DoSubTask2c();
    if (cancelToken.IsSet)
        return;
}

Is there a better solution? I was toying for something like a SoLongAs statement that would automatically pepper the checks in and automatically and raise an internal exception if the condition was met, which would be internally caught at the end of the loop, eg:

void ThreadWorkerHandler(CancelToken cancelToken)
{
    SoLongAs (canelToken.IsSet == false)
    {
        DoTask1(cancelToken);
        DoTask2(cancelToken);
        DoTask3(cancelToken);
        DoTask4(cancelToken);
    }
}

But I imagine that wouldn't work for some reason, also more importantly I doubt something like this actually exists. If not is there a better way to handle this scenario than I am currently using? Thanks.


Solution

  • If you have a collection of delegates that represent your work you can get something that looks pretty close to your code snippet. It has a bit more overhead than your intented syntax, but the key point is that it's a constant overhead, rather than a per-line overhead.

    List<Action> actions = new List<Action>()
    {
        ()=> DoTask1(cancelToken),
        ()=> DoTask2(cancelToken),
        ()=> DoTask3(cancelToken),
        ()=> DoTask4(cancelToken),
    };
    
    foreach(var action in actions)
    {
        if (!cancelToken.IsSet)
            action();
    }