Chaining asynchronous tasks that must run sequentially I want to implement an Agent-like object in C#. An Agent wraps some memory location (ideally storing an immutable object) and receives updates to that location. All these updates are performed asynchronously, but sequentially. In other words


  At any point in time, at most one [update submitted to an] Agent is
  being executed.


My current implementation uses the TPL. The Agent keeps a reference to the last update that has been or must be performed, wrapped in a Task. When submitted a new update, you wrap the update in a Task, get a reference to the last Task and replace it, atomically, then invoke ContinueWith on that last Task with this new update Task (everything submitted through a TaskScheduler).

public class Agent&lt;T&gt; where T : class
{
    private T wrappedValue;
    private readonly TaskScheduler scheduler;
    private Task lastTask;

    public Agent (T value, TaskScheduler scheduler)
    {
        this.wrappedValue = value;
        this.scheduler = scheduler;
    }

    public void Send (Action&lt;T&gt; operation)
    {
        Task task = new Task ((action) =&gt; ((Action&lt;T&gt;)action) (wrappedValue), operation);
        Task localCurrent;
        do {
            // always append to the end
            localCurrent = lastTask;
        } while(Interlocked.CompareExchange (ref lastTask, task, localCurrent) != localCurrent);

        if (localCurrent == null) {
            task.Start (scheduler);
        } else {
            localCurrent.ContinueWith ((currentTask, scheduler) =&gt; {
                task.Start ((TaskScheduler)scheduler);
            }, scheduler, scheduler);
        }
    }
    [...]
}


The first thread to send an action will see null in the field and so needs to Start() the Task directly.

This (seems to) work. Only one submitted action is ever running at a given moment. Additionally, tasks are potentially run on different threads. I don't want a dedicated thread for updates (and I don't want to lock up a TaskScheduler thread). 

However, I'm worried about a few things that I think could be improved:


I'd like to be able to ContinueWith the Task task directly, instead of having to start it within a different submitted Action. I feel like I need to create the new Task so that I can atomically exchange the lastTask field's value.
Lambda capturing of local variables, partly alleviated with state objects where possible.
How can I keep a reference to the currently executing Task, so I can short-circuit execution? Or should I use a poison pill to signal termination and not chain any new tasks?
Interlocked for atomicity. Should I use a ConcurrentQueue? How would I manage not having a dedicated thread and only running one Task at a time with a ConcurrentQueue?
Is a Volatile.Read of lastTask necessary for localCurrent = lastTask;?




Doing this now

private void InvokeAndComplete (Action&lt;T&gt; act, TaskCompletionSource&lt;byte&gt; promise)
{
    try {
        act (internalValue);
    } finally {
        // some value we will ignore
        promise.SetResult (1);
    }
}

public Task Alter (Action&lt;T&gt; alterAction)
{
    TaskCompletionSource&lt;byte&gt; promise = new TaskCompletionSource&lt;byte&gt; ();
    Task localLastTask;
    do {
        // Not sure if Volatile.Read is necessary
        localLastTask = Volatile.Read (ref lastTask);
    } while (Interlocked.CompareExchange (ref lastTask, promise.Task, localLastTask) != localLastTask);
    // only this thread has a reference to the object referenced by localLastTask
    return localLastTask.ContinueWith ((currentTask) =&gt; {
        InvokeAndComplete (alterAction, promise);
    }, taskScheduler);
}
I would use lock instead of Interlocked, because its much easier to follow and debug. Unless you are after some kind of micro optimization, I think you should keep it simple:

lock (lock)
{
    if (lastTask == null)
    {
       task.Start(scheduler);
    } 
    else 
    {
        lastTask.ContinueWith(...);
    }
    lastTask = task;
}

I think you can modify your lambda expression to get a current task:

Task task = null;
task = new Task ((action) =&gt; 
     {
         CurrentTask = task;
         ((Action&lt;T&gt;)action) (wrappedValue);
     }, operation);


Though I am not too experienced with TPL, so there might be an easier way.