Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Queuing up async code to execute sequentially

Tags:

c#

async-await

At any time, I can receive a method call that will require a long-running operation to satisfy.

There are several of these methods. Because they share a resource, it's important that they don't run concurrently - each should run in sequence.

Normally, I'd simply make the calls in sequence:

var result1 = await Foo1Async();
var result2 = await Foo2Async();

However, in this case, the method calls to me are asynchronous:

void Bar1()
{
    var result = await Foo1Async();
    // ...
}

void Bar2()
{
    var result = await Foo2Async();
    // ...
}

I think I need to use Task.ContinueWith, and have come up with this:

readonly object syncLock = new Object();

private Task currentOperationTask = TaskUtilities.CompletedTask;
public Task<TResult> EnqueueOperation<TResult>(Func<Task<TResult>> continuationFunction)
{
    lock (this.syncLock)
    {
        var operation = this.currentOperationTask.ContinueWith(predecessor => continuationFunction()).Unwrap();
        this.currentOperationTask = operation;
        return operation;
    }
}

And I use it like this:

void Bar1()
{
    var result = await EnqueueOperation(() => Foo1Async());
    // ...
}

void Bar2()
{
    var result = await EnqueueOperation(() => Foo2Async());
    // ...
}

Is this the right way to approach this problem? Are there any pitfalls I should be aware of?

like image 805
Jay Bazuzi Avatar asked Jul 07 '13 23:07

Jay Bazuzi


1 Answers

Your code will work fine, I think this is a reasonable way to solve the problem.

A simpler approach to do this would be to use AsyncLock from Nito AsyncEx:

private readonly AsyncLock asyncLock = new AsyncLock();

public async Task<TResult> EnqueueOperation<TResult>(
    Func<Task<TResult>> continuationFunction)
{
    using (await asyncLock.LockAsync())
    {
        return await continuationFunction();
    }
}
like image 90
svick Avatar answered Oct 15 '22 08:10

svick