Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Run only the current task and the most recently queued task

I have some methods. We'll call them "TurnOn" and "TurnOff". Each returns an unstarted task (as in System.Threading.Tasks.Task). Once started, the task takes about a second to complete. I have some code to queue these with the Task.ContinueWith method (shown here). The problem is that I only really want the currently running task (if any) plus the most recently requested task to run. Presently, if my user toggles the on/off switch ten times in a second, it takes twenty seconds for my code to catch up. It's only the final request that I care about. How can I achieve this? I can't see a way inside the ContinueWith method to know if there is another ContinueWith method after it. I've been looking at the example here, but can't quite see how to apply it.

Update: this is now available in the library here: https://github.com/BrannonKing/Kts.ActorsLite . It's named "MostRecent..."

like image 207
Brannon Avatar asked Feb 10 '14 17:02

Brannon


1 Answers

Just create a class that keeps track of the currently executing task, the action to be run next, and that runs the "next" action whenever the previous action finishes. The key here is that if the next action has a value when you go to enqueue a new one you can simply replace nextTask rather than adding another continuation.

public class TaskQueue
{
    private Task currentlyExecuting = Task.FromResult(false);
    public Task continuation = null;
    private CancellationTokenSource cts = new CancellationTokenSource();
    private Action nextTask = null;
    private object key = new object();
    public Task Queue(Action action)
    {
        lock (key)
        {
            if (nextTask == null)
            {
                nextTask = action;
                ScheduleContinuation();
                return continuation;
            }
            else
            {
                cts.Cancel();
                nextTask = action;
                ScheduleContinuation();
                return continuation;
            }
        }
    }

    private void ScheduleContinuation()
    {
        cts = new CancellationTokenSource();
        var token = cts.Token;
        continuation = currentlyExecuting.ContinueWith(t =>
        {
            lock (key)
            {
                currentlyExecuting = Task.Run(nextTask);
                token.ThrowIfCancellationRequested();
                nextTask = null;
            }
        }, cts.Token);
    }
}

While you could accept unstarted tasks here, and it's a pretty trivial re-write to do so, I consider it better practice here to accept a delegate and to create and then start the task when it is ready to be started.

like image 123
Servy Avatar answered Oct 17 '22 02:10

Servy