HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Chaining asynchronous tasks that must run sequentially

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
mustasynchronouschainingtasksthatrunsequentially

Problem

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 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 operation)
    {
        Task task = new Task ((action) => ((Action)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) => {
                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).

Solution

-
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) => 
     {
         CurrentTask = task;
         ((Action)action) (wrappedValue);
     }, operation);


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

Code Snippets

lock (lock)
{
    if (lastTask == null)
    {
       task.Start(scheduler);
    } 
    else 
    {
        lastTask.ContinueWith(...);
    }
    lastTask = task;
}
Task task = null;
task = new Task ((action) => 
     {
         CurrentTask = task;
         ((Action<T>)action) (wrappedValue);
     }, operation);

Context

StackExchange Code Review Q#97199, answer score: 3

Revisions (0)

No revisions yet.