Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make a method cancelable without it becoming ugly?

I am currently in the process of retrofitting our long-running methods to be cancelable. I am planning on using System.Threading.Tasks.CancellationToken to implement that.

Our methods generally perform a few long-running steps (sending commands to and then waiting for hardware mostly), e.g.

void Run()
{
    Step1();
    Step2();    
    Step3();
}

My first (maybe stupid) thought on cancellation would transform this into

bool Run(CancellationToken cancellationToken)
{
    Step1(cancellationToken);

    if (cancellationToken.IsCancellationRequested)
        return false;

    Step2(cancellationToken);

    if (cancellationToken.IsCancellationRequested)
        return false;    

    Step3(cancellationToken);

    if (cancellationToken.IsCancellationRequested)
        return false;

    return true;
}

which frankly looks horrible. This "pattern" would continue inside the single steps, too (and they are necessarily rather longish already). This would make Thread.Abort() look rather sexy, although I know its not recommended.

Is there a cleaner pattern to achieve this that does not hide away the application logic beneath lots of boilerplate code?

Edit

As an example for the nature of the steps, the Run method could read

void Run()
{
    GiantRobotor.MoveToBase();
    Oven.ThrowBaguetteTowardsBase();    
    GiantRobotor.CatchBaguette();
    // ...
}

We are controlling different hardware units that need to be synchronized to work together.

like image 478
Jens Avatar asked Nov 05 '13 14:11

Jens


2 Answers

If the steps are somehow independend regarding the dataflow within the method, but can't be executed in a parallel matter, the following approach may be better readable:

void Run()
{
    // list of actions, defines the order of execution
    var actions = new List<Action<CancellationToken>>() {
       ct => Step1(ct),
       ct => Step2(ct),
       ct => Step3(ct) 
    };

    // execute actions and check for cancellation token
    foreach(var action in actions)
    {
        action(cancellationToken);

        if (cancellationToken.IsCancellationRequested)
            return false;
    }

    return true;
}

If the steps don't need the cancellation token because you can split them up in tiny units, you can even write a smaller list definition:

var actions = new List<Action>() {
    Step1, Step2, Step3
};
like image 113
Matten Avatar answered Oct 19 '22 08:10

Matten


what about continuation?

var t = Task.Factory.StartNew(() => Step1(cancellationToken), cancellationToken)
   .ContinueWith(task => Step2(cancellationToken), cancellationToken, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current)
   .ContinueWith(task => Step3(cancellationToken), cancellationToken, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current);
like image 23
Nickolai Nielsen Avatar answered Oct 19 '22 08:10

Nickolai Nielsen