将异步代码排队以按顺序执行

8

随时都可能接到需要进行长时间操作的方法调用。

这些方法有几个。因为它们共享一个资源,所以重要的是它们不应该同时运行 - 每个方法应该按顺序运行。

通常情况下,我会按顺序依次进行调用:

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

然而,在这种情况下,我的方法调用是异步的:
void Bar1()
{
    var result = await Foo1Async();
    // ...
}

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

我认为我需要使用Task.ContinueWith,并想出了以下代码:

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;
    }
}

我是这样使用它的:

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

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

这是正确的解决问题的方法吗?有什么我需要注意的陷阱吗?


我对此没有任何问题,但是如果你将签名改为(Task<TResult> continuationFunction),你可以省略() => - SimpleVar
@YoryeNathan 这样做行不通,因为这意味着每个异步方法都会立即开始执行,问题是如何避免这种情况。 - svick
顺便说一句,如果你的代码真的看起来像这样,你也可以只写 await EnqueueOperation(Foo1Async);(不需要 lambda)。 - svick
1个回答

1
你的代码将会很好地运行,我认为这是解决问题的合理方法。
一个更简单的方法是使用{{link1:Nito AsyncEx中的AsyncLock}}:
private readonly AsyncLock asyncLock = new AsyncLock();

public async Task<TResult> EnqueueOperation<TResult>(
    Func<Task<TResult>> continuationFunction)
{
    using (await asyncLock.LockAsync())
    {
        return await continuationFunction();
    }
}

这个会等待与 EnqueueOperation 调用无关的任务吗? - SimpleVar
@YoryeNathan 不会。但是如果你从UI线程调用EnqueueOperation(),它也会在那里调用continuationFunction,这可能不是你想要的。 - svick

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接