队列调用异步方法

mbu*_*ill 5 c# queue async-await

我有一个名为Refresh的异步操作.如果在第一次完成之前进行第二次刷新调用,我需要对其进行排队.这就是我所拥有的:

public async Task Refresh(RefreshArgs refreshArgs)
{
    await EnqueueRefreshTask(refreshArgs);
}

private Queue<RefreshArgs> refreshQueue =
    new Queue<RefreshArgs>();

private async Task EnqueueRefreshTask(RefreshArgs refreshArgs)
{
    refreshQueue.Enqueue(refreshArgs);

    await ProcessRefreshQueue();
}

private Task currentRefreshTask = null;

private async Task ProcessRefreshQueue()
{
    if ((currentRefreshTask == null) || (currentRefreshTask.IsCompleted))
    {
        if (refreshQueue.Count > 0)
        {
            var refreshArgs = refreshQueue.Dequeue();

            currentRefreshTask = DoRefresh(refreshArgs);
            await currentRefreshTask;

            await ProcessRefreshQueue();
        }           
    }
}

private async Task DoRefresh(RefreshArgs refreshArgs)
{
    // Lots of code here, including calls to a server that are executed with await.
    // Code outside my control may make another Refresh(args) call while this one is still processing.
    // I need this one to finish before processing the next.
}
Run Code Online (Sandbox Code Playgroud)

它有效,但我不确定这是使用Tasks执行此操作的最佳方法.有什么想法吗?

更新:

我尝试使用ActionBlock:

public async Task Refresh(RefreshArgs refreshArgs)
{
    if (refreshActionBlock == null)
    {
        var executionDataflowBlockOptions = new ExecutionDataflowBlockOptions();
        executionDataflowBlockOptions.MaxMessagesPerTask = 1;
        executionDataflowBlockOptions.TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();

        refreshActionBlock = new ActionBlock<RefreshArgs>(args => DoRefresh(args), executionDataflowBlockOptions);
    }

    await refreshActionBlock.SendAsync(refreshArgs);
}
Run Code Online (Sandbox Code Playgroud)

这会将DoRefresh排队,并允许它在UI线程(我需要)中运行.问题是SendAsync没有等待DoRefresh的工作.

SendAsync:"异步向目标消息块提供消息,允许推迟".我只是在等待发送,而不是动作本身.

这样做不会按预期工作:

await Refresh(RefreshArgs.Something);
// other code goes here. It expects the first refresh to be finished.
await Refresh(RefreshArgs.SomethingElse);
// other code goes here. It expects the second refresh to be finished.
Run Code Online (Sandbox Code Playgroud)

ActionBlock将排队第二次刷新,但等待刷新完成之前等待.当DoRefresh的工作完成时,我需要它们返回.

svi*_*ick 3

我认为最简单的方法是使用AsyncLock. 您可以从 Stephen Cleary 的 AsyncEx 库中获取一个,或者您可以阅读Stephen Toub 的关于如何自己构建它的文章

当你有了AsyncLock,实现你的Refresh()就很简单了:

public async Task Refresh(RefreshArgs refreshArgs)
{
    using (await m_lock.LockAsync())
    {
        // do your async work here
    }
}
Run Code Online (Sandbox Code Playgroud)

这将确保Refresh()es 一个接一个地执行(而不是交错),并且确保Taskfrom 的返回Refresh()仅在Refresh()实际完成后才完成。

可以使用ActionBlockTPL Dataflow 来执行相同的操作,但您还需要使用它TaskCompletionSource,并且它会比版本复杂得多AsyncLock