TPL Dataflow and async/await vs CCR - part 6

Same co-worker as the other day pointed out an important difference between how CCR and TPL data flow deals with exclusive schedulers as described in the end of part 4. To illustrate, assume you have the following test code:

   1: [TestMethod]
  2: public void TestExclusiveExecution()
  3: {
  4:     var options = new ExecutionDataflowBlockOptions() 
  5:         { TaskScheduler = 
  6:             new ConcurrentExclusiveSchedulerPair().ExclusiveScheduler };
  7:     var p1 = new ActionBlock<int>(item => ProcessItem(1, item), options);
  8:     var p2 = new ActionBlock<int>(item => ProcessItem(2, item), options);
  9:     p1.Post(42);
 10:     p2.Post(4711);
 11:     Task.Delay(TimeSpan.FromSeconds(6)).Wait();
 12: }

If you have what I would describe as the default implementation looking like this:

  13: private async Task ProcessItem(int id, int i)
 14: {
 15:     Console.WriteLine("ProcessItem {0} - Start - {1}", id, i);
 16:     await Task.Delay(TimeSpan.FromSeconds(1));
 17:     Console.WriteLine("ProcessItem {0} - Working - {1}", id, i);
 18:     await Task.Delay(TimeSpan.FromSeconds(1));
 19:     Console.WriteLine("ProcessItem {0} - End - {1}", id, i);
 20: }

Then the console output looks like this:

Which is definitely not what you expect coming from the CCR world. As you can see only one task is executed at any given time but as one task is awaiting a delay the other executes. This is bad if you're using the exclusive scheduler options as kind of a lock which is kind of the CCR way. It is however good if you assume that only the code in your handlers needs to be exclusive but any asynchronous tasks they need to complete can be waited on while another handler executes. However if you want your handler to behave more like in CCR this is one way of doing it:

  21: private void ProcessItem(int id, int i)
 22: {
 23:     Console.WriteLine("ProcessItem {0} - Start - {1}", id, i);
 24:     Task.Delay(TimeSpan.FromSeconds(1)).Wait();
 25:     Console.WriteLine("ProcessItem {0} - Working - {1}", id, i);
 26:     Task.Delay(TimeSpan.FromSeconds(1)).Wait();
 27:     Console.WriteLine("ProcessItem {0} - End - {1}", id, i);
 28: }

In the result you can now see that each handler is completing before the other is executed.