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的工作完成时,我需要它们返回.
我认为最简单的方法是使用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 一个接一个地执行(而不是交错),并且确保Task
from 的返回Refresh()
仅在Refresh()
实际完成后才完成。
您可以使用ActionBlock
TPL Dataflow 来执行相同的操作,但您还需要使用它TaskCompletionSource
,并且它会比版本复杂得多AsyncLock
。