Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I create a scheduler which never executes more than one Task at a time using async-await?

I want to implement a class or pattern that ensures that I never execute more than one Task at a time for a certain set of operations (HTTP calls). The invocations of the Tasks can come from different threads at random times. I want to make use of the async-await pattern so that the caller can handle exceptions by wrapping the call in a try-catch.

Here's an illustration of the intended flow of execution:

enter image description here

Pseudo code from caller:

try {
    Task someTask = GetTask();
    await SomeScheduler.ThrottledRun(someTask);
} 
catch(Exception ex) { 
    // Handle exception
}

The Taskclass here might instead be an Action class depending on the solution.

Note that I when I use the word "Schedule" in this question I'm not necessarily using it with relation to the .NET Task Scheduler. I don't know the async-await library well enough to know at what angle and with what tools to approach this problem. The TaskScheduler might be relevant here, and it may not. I've read the TAP pattern document and found patterns that almost solve this problem, but not quite (the chapter on interleaving).

like image 890
Nilzor Avatar asked Aug 22 '12 07:08

Nilzor


1 Answers

There is a new ConcurrentExclusiveSchedulerPair type in .NET 4.5 (I don't remember if it was included in the Async CTP), and you can use its ExclusiveScheduler to restrict execution to one Task at a time.

Consider structuring your problem as a Dataflow. It's easy to just pass a TaskScheduler into the block options for the parts of the dataflow you want restricted.

If you don't want to (or can't) use Dataflow, you can do something similar yourself. Remember that in TAP, you always return started tasks, so you don't have the "creation" separated from the "scheduling" like you do in TPL.

You can use ConcurrentExclusiveSchedulerPair to schedule Actions (or async lambdas without return values) like this:

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

There are a few things to note about this:

  • A single instance of ConcurrentExclusiveSchedulerPair only coordinates Tasks that are queued to its schedulers. A second instance of ConcurrentExclusiveSchedulerPair would be independent from the first, so you have to ensure the same instance is used in all parts of your system you want coordinated.
  • An async method will - by default - resume on the same TaskScheduler that started it. So this means if one async method calls another async method, the "child" method will "inherit" the parent's TaskScheduler. Any async method may opt out of continuing on its TaskScheduler by using ConfigureAwait(false) (in that case, it continues directly on the thread pool).
like image 102
Stephen Cleary Avatar answered Sep 17 '22 21:09

Stephen Cleary