如何使用async-await创建一个调度程序,使其永远不会同时执行多个任务?

20
我想实现一个类或模式,以确保我永远不会为某些操作(HTTP调用)执行超过一个任务。这些任务的调用可能来自不同的线程,并在随机时间进行。我希望利用异步等待模式,以便调用者可以通过将调用包装在try-catch中来处理异常。
这是预期执行流程的示意图: enter image description here 来自调用者的伪代码:
try {
    Task someTask = GetTask();
    await SomeScheduler.ThrottledRun(someTask);
} 
catch(Exception ex) { 
    // Handle exception
}

根据解决方案,这里的Task类可能会被替换为一个Action类。

请注意,当我在本问题中使用“Schedule”一词时,并不一定是针对.NET任务计划程序来使用的。我不太熟悉async-await库,不知道该如何以及用什么工具来解决这个问题。TaskScheduler可能与此相关,也可能不相关。我已经阅读了TAP模式文档,并找到了几乎解决这个问题的模式(交错章节),但还不完全符合要求。

1个回答

31

在.NET 4.5中有一个新的类型ConcurrentExclusiveSchedulerPair(我不记得它是否包含在Async CTP中),您可以使用它的ExclusiveScheduler将执行限制为一次Task

考虑将问题构建为数据流。只需将TaskScheduler传递到要受限制的数据流部分的块选项中即可。

如果您不想(或无法)使用Dataflow,您可以自己做类似的事情。请记住,在TAP中,您总是返回已启动的任务,因此与TPL中将“创建”与“调度”分开不同。

您可以像这样使用ConcurrentExclusiveSchedulerPair来调度Action(或没有返回值的async lambda):

public static ConcurrentExclusiveSchedulerPair schedulerPair =
    new ConcurrentExclusiveSchedulerPair();
public static TaskFactory exclusiveTaskFactory =
    new TaskFactory(schedulerPair.ExclusiveScheduler);
...
public static Task RunExclusively(Action action)
{
  return exclusiveTaskFactory.StartNew(action);
}
public static Task RunExclusively(Func<Task> action)
{
  return exclusiveTaskFactory.StartNew(action).Unwrap();
}

关于这个问题需要注意以下几点:

  • ConcurrentExclusiveSchedulerPair 的单一实例只协调其调度器队列中的Task。第二个ConcurrentExclusiveSchedulerPair 实例将独立于第一个实例,因此您必须确保在系统的所有部分中使用相同的实例来协调。
  • 默认情况下,async 方法将在启动它的 TaskScheduler 上恢复执行。这意味着如果一个async方法调用另外一个async方法,则“子”方法会“继承”父方法的TaskScheduler。任何async方法都可以通过使用ConfigureAwait(false)来选择不在其TaskScheduler上继续执行(在这种情况下,它将直接在线程池上运行)。

1
当我使用Action重载时,我可以让你的示例工作,但是当我发送Func<Task>时却不能。如果你能解释一下为什么这个Gist中的单元测试不起作用,我会非常感激:https://gist.github.com/3424332 - Nilzor
6
Func<Task>无法这样工作,因为ConcurrentExclusiveSchedulerPair一次只允许执行一个任务,因此当async方法使用await时,它不再执行。如果需要阻塞直到整个async方法完成,最好使用async - Stephen Cleary
1
谢谢您的帮助。我开始意识到“async”和“await”这两个关键字只是冰山一角,需要数月时间才能探索清楚。 - Nilzor
5
如果你在async操作中使用ExclusiveScheduler,请注意多个操作的执行可能会交错。因此,当第一个操作暂停时,第二个操作可能开始执行。但是,第一个操作的剩余部分只有在第二个操作暂停或完成后才会继续执行。如果这不是你想要的结果,可以使用带有MaxDegreeOfParallelism = 1参数的TPL Dataflow块。 - svick
1
@Asad:是的,这就是任务调度程序的工作方式。它们安排由任务表示的工作单元。一个async方法可能是多个工作单元,由await语句分隔开。为什么不提出你自己的问题,注意避免A/B问题(即描述你正在尝试解决的实际问题)? - Stephen Cleary
显示剩余6条评论

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