Udostępnij za pośrednictwem


CCR tips and tricks - part 17

Sometimes when you have a number of things that have to be executed in sequence a common pattern is to have some kind of status variable to track errors and abort the sequence when there is an error. There is however another pattern that works well not only with sequential work but also works for scatter-gather operations; the abort port (say that fast and it sounds like fake Swedish). Here is an example of a method to calculate a Fibonacci number that can be aborted:

   1: public void FibonacciAbortable(
  2:     int n, 
  3:     Port<int> resultPort, 
  4:     Port<Exception> abortPort)
  5: {
  6:     if (n < 0)
  7:     {
  8:         abortPort.Post(new ArgumentOutOfRangeException("n"));
  9:     }
 10:     else if (n <= 1)
 11:     {
 12:         resultPort.Post(n);
 13:     }
 14:     else
 15:     {
 16:         int n1 = -1;
 17:         Port<int> n1port = new Port<int>();
 18:         Port<int> n2port = new Port<int>();
 19:         Arbiter.Activate(
 20:             dispatcherQueue, 
 21:             Arbiter.FromHandler(
 22:                 () => this.FibonacciAbortable(n - 1, n1port, abortPort)));
 23:         Arbiter.Activate(
 24:             dispatcherQueue,
 25:             Arbiter.Choice(
 26:                 Arbiter.Receive(
 27:                     false,
 28:                     n1port,
 29:                     s =>
 30:                         {
 31:                             n1 = s;
 32:                             Arbiter.Activate(
 33:                                 dispatcherQueue,
 34:                                 Arbiter.FromHandler(
 35:                                     () => this.FibonacciAbortable(
 36:                                         n - 2, n2port, abortPort)));
 37:                         }),
 38:                 Arbiter.Receive(false, abortPort, abortPort.Post)));
 39:         Arbiter.Activate(dispatcherQueue, Arbiter.Choice(
 40:             Arbiter.Receive(false, n2port, s => resultPort.Post(s + n1)),
 41:             Arbiter.Receive(false, abortPort, abortPort.Post)));
 42:     }
 43: }

There are four important tricks to consider when you use an abort port:

  • If you have an error port, you want to post the error to the abort port too since it's a good pattern to let the message on the abort port be the reason for the abort.
  • All handlers for the abort port must repost the message to the same port so that any number of tasks can be aborted with just one abort item. This means that once all tasks have been aborted there should be once item in the abort port queue.
  • If you wrap code you do not know will repost to the abort port you should not use your own abort port for child tasks; let child tasks have their own abort port.
  • The use of an abort port will not guarantee that tasks are canceled, only things you ignore because of an abort port will be ignored. it also means that scheduled tasks will post responses so the use of a choice is important to prevent other handlers form executing.