Being Cellfish

Stuff I wished I've found in some blog (and sometimes did)

July, 2011

Change of Address
This blog has moved to blog.cellfish.se.
Posts
  • Being Cellfish

    CCR tips and tricks - part 15

    • 3 Comments

    In part 14 I showed you how to work with an asynchronous API from CCR. Today we'll handle synchronous code form CCR. Since one of the most common ways to work with CCR is to use a dispatcher with one thread per core you do not want to block one thread by waiting on some synchronous, long running operation. Let's create a blocking operation first:

    private void BlockingMethod(int seconds)
    {
        Thread.Sleep(TimeSpan.FromSeconds(seconds));
    }

    Calling this from a CCR thread would in most cases be devastating for performance of your code. If this method is called multiple times from different CCR tasks you could easily have all your CCR threads blocked in this method and that is probably not what you want. But we can fix this by using a feature of the DispatcherQueue. If you create a DispatcherQueue with no arguments it will not use a default dispatcher (with one thread per core), it will use the CLR thread pool! A good pattern to use if you do not know which dispatcher your code will be used with (this might be true if you have a library of functions for others to use) is demonstrated in this code:

      1: private IEnumerator<ITask> MethodThatNeedToCallBlockingMethodFromCcr(
      2:     Port<EmptyValue> donePort)
      3: {
      4:     if (!Thread.CurrentThread.IsThreadPoolThread)
      5:     {
      6:         using (var clrTaskQueue = new DispatcherQueue())
      7:         {
      8:             yield return
      9:                 Arbiter.ExecuteToCompletion(
     10:                     clrTaskQueue,
     11:                     new IterativeTask<Port<EmptyValue>>(
     12:                         donePort,
     13:                         MethodThatNeedToCallBlockingMethodFromCcr));
     14:             yield break;
     15:         }
     16:     }
     17:  
     18:     BlockingMethod(42);
     19:     donePort.Post(EmptyValue.SharedInstance);
     20: }
    

    There are two reasons for the guard clause in the beginning that reschedules the task on a CLR thread;

    • You do not want to reschedule if already on a CLR thread since every time you schedule a task there is a small overhead.
    • You do not want to use the CLR thread pool for all of your CCR code since using the CLR thread pool dispatcher queue has a greater overhead for each task executed than using a standard CCR dispatcher queue with a more limited dispatcher. CCR has very small overhead when running with one thread per core.
  • Being Cellfish

    CCR tips and tricks - part 6

    • 2 Comments

    Today I have three recommendations for your CCR methods. For methods that are public interfaces to components I would recommend methods that returns ports like this:

     1: public PortSet<ResultType, Exception> DoSomething()

    This makes it easy for the consumer (yourself or other developers) to just call the method and then use the port returned. As a consumer I do not need to know how to create the port needed and I think that is convenient. It also means the implementation may complete synchronously or asynchronously. As a caller I don't really care.

    For internal helpers I however tend to use the following two variants:

     1: private IEnumerator<ITask> DoSomething(PortSet<ResultType, Exception> resultPort)
     2: private void DoSomething(PortSet<ResultType, Exception> resultPort)

    The first one should only be used if the method itself needs to yield return. If it ends up having a yield break in the end just to pass compilation you should go for the second variant. However if the method is part of an interface definition I always use the first (iterator) variant.

    So wait you may think. isn't an interface public and hence should have the variant returning a port? And the answer is yes, if the interface is public. But if the interface is internal and it may make sense to treat it as an internal helper.

  • Being Cellfish

    CCR tips and tricks - part 8

    • 0 Comments

    The scatter gather pattern where you spawn multiple tasks and then wait for them all to complete is a common pattern used with CCR. if you have a small, well known number of tasks to spawn the use of a joined receiver combined with a choice for errors can be a slick way of achieving "wait for all to succeed or the first failure". That is assuming that one failure means the whole scatter and gather operation failed.

      1: public IEnumerator<ITask> Fibonacci(
      2:     int n, 
      3:     PortSet<int, Exception> resultPort)
      4: {
      5:     if (n < 0)
      6:     {
      7:         resultPort.Post(new ArgumentOutOfRangeException("n"));
      8:     }
      9:     else if (n <= 1)
     10:     {
     11:         resultPort.Post(n);
     12:     }
     13:     else
     14:     {
     15:         var n1port = Fibonacci(n - 1);
     16:         var n2port = Fibonacci(n - 2);
     17:         var joinedSuccess = Arbiter.JoinedReceive<int, int>(
     18:             false, 
     19:             n1port, 
     20:             n2port, 
     21:             (n1, n2) => resultPort.Post(n1 + n2));
     22:         yield return Arbiter.Choice(
     23:             joinedSuccess,
     24:             Arbiter.Receive<Exception>(false, n1port, resultPort.Post),
     25:             Arbiter.Receive<Exception>(false, n2port, resultPort.Post));
     26:     }
     27: }
    

    This implementations computes a specific Fibonacci number by computing the two parts in parallel. Not the most efficient way to do this but it shows how a joined receiver is used to collect the result but if there is any error it is returned immediately without waiting for the two results. This does not mean that "the other" task is aborted. More on that in a future post in this series.

  • Being Cellfish

    CCR tips and tricks - part 9

    • 0 Comments

    The pattern in CCR to use iterators to implements tasks is very powerful since it means that the code of the task looks very synchronous even though it performs asynchronous tasks. But sometimes you may yield on the wrong thing and the resulting behavior is unexpected. But even before you use an iterative handler (as they're called in CCR) you should ask yourself if you really need it. The rule of thumb is fairly easy. If you need a "yield break" in the end of your method to make it compile (since there are no "yield return" in it) you should not use an iterative handler. The exception is if you implement some interface or base implementation that others will override and where you anticipate those implementations to need an iterative handler. The reason to not use an iterative handler unless needed is that there is a slight performance penalty from using iterative handlers so if you don't need them, don't pay the price.

    In the following code examples I will be reusing the following simple methods/variables (and yes I have an iterative handler that should not be it, but it's used to show a difference between iterative handlers and regular handlers.

      1: private DispatcherQueue dispatcherQueue = new DispatcherQueue();
      2: private bool didSomething = false;
      3: private bool didSomethingElse = false;
      4:  
      5: private void DoSomething()
      6: {
      7:     didSomething = true;
      8: }
      9:  
     10: private IEnumerator<ITask> DoSomethingElse()
     11: {
     12:     didSomethingElse = true;
     13:     yield break;
     14: }
    

    Now look at the following test. Notice that the iterative task is never executed. This is because yielding on a regular task executes that task but never returns to the original method.

      1: private IEnumerator<ITask> DoItAllWrongWay(ManualResetEvent mre)
      2: {
      3:     yield return Arbiter.FromHandler(DoSomething);
      4:     yield return Arbiter.FromIteratorHandler(DoSomethingElse);
      5:     mre.Set();
      6: }
      7:  
      8: [TestMethod]
      9: public void Doing_the_wrong_thing()
     10: {
     11:     var mre = new ManualResetEvent(false);
     12:     Arbiter.Activate(
     13:         dispatcherQueue, 
     14:         Arbiter.FromIteratorHandler(() => this.DoItAllWrongWay(mre)));
     15:     Assert.IsFalse(
     16:         mre.WaitOne(TimeSpan.FromSeconds(3)), 
     17:         "Should not complete tasks");
     18:     Assert.IsTrue(didSomething);
     19:     Assert.IsFalse(didSomethingElse);
     20: }
    

    Now look at what happens when we yield return on the iterative handler first instead. Now both helper methods execute but we still don't complete the calling method since yielding on the regular handler never returns.

      1: private IEnumerator<ITask> DoItWrongButItWorksAlmost(ManualResetEvent mre)
      2: {
      3:     yield return Arbiter.FromIteratorHandler(DoSomethingElse);
      4:     yield return Arbiter.FromHandler(DoSomething);
      5:     mre.Set();
      6: }
      7:  
      8: [TestMethod]
      9: public void Doing_the_wrong_thing_but_it_almost_works()
     10: {
     11:     var mre = new ManualResetEvent(false);
     12:     Arbiter.Activate(
     13:         dispatcherQueue, 
     14:         Arbiter.FromIteratorHandler(() => this.DoItWrongButItWorksAlmost(mre)));
     15:     Assert.IsFalse(
     16:         mre.WaitOne(TimeSpan.FromSeconds(3)), 
     17:         "Should not complete tasks");
     18:     Assert.IsTrue(didSomething);
     19:     Assert.IsTrue(didSomethingElse);
     20: }
    

    Last we'll look at the correct way of dealing with this scenario.

      1: private IEnumerator<ITask> DoItRight(ManualResetEvent mre)
      2: {
      3:     yield return Arbiter.ExecuteToCompletion(
      4:         dispatcherQueue, new Task(DoSomething));
      5:     yield return new IterativeTask(DoSomethingElse);
      6:     mre.Set();
      7: }
      8:  
      9: [TestMethod]
     10: public void Doing_the_right_thing()
     11: {
     12:     var mre = new ManualResetEvent(false);
     13:     Arbiter.Activate(
     14:         dispatcherQueue, 
     15:         Arbiter.FromIteratorHandler(() => this.DoItRight(mre)));
     16:     Assert.IsTrue(mre.WaitOne(TimeSpan.FromSeconds(3)), "Should complete tasks");
     17:     Assert.IsTrue(didSomething);
     18:     Assert.IsTrue(didSomethingElse);
     19: }
    

    Note that for iterative tasks you may actually use an alternative syntax (line 5) when yielding to an iterative task.

  • Being Cellfish

    CCR tips and tricks - part 10

    • 0 Comments

    Today we'll continue using the same base helpers as in part 9 and today we'll look at a common mistake made in iterative handlers. Take a look at the following code:

      1: private IEnumerator<ITask> ForgettingToYield(ManualResetEvent mre)
      2: {
      3:     var port = new Port<EmptyValue>();
      4:     port.Post(EmptyValue.SharedInstance);
      5:     Arbiter.Receive(false, port, v => mre.Set());
      6:     yield break;
      7: }
      8:         
      9: [TestMethod]
     10: public void Forgetting_to_yield()
     11: {
     12:     var mre = new ManualResetEvent(false);
     13:     Arbiter.Activate(
     14:         dispatcherQueue, 
     15:         Arbiter.FromIteratorHandler(() => this.ForgettingToYield(mre)));
     16:     Assert.IsFalse(
     17:         mre.WaitOne(TimeSpan.FromSeconds(3)), "Should not complete tasks");
     18: }
    

    Note the mistake on line 5. Since the task created is not yielded on it will not execute. In my experience these type of errors are easily found while writing unit tests. They look weird sine you think "why is this not executing" even though the code "look" right.

  • Being Cellfish

    CCR tips and tricks - part 11

    • 0 Comments

    A very common pattern in CCR is the use of a result port. That means that you have a port you give (or get from) a method you want to call and that method will post a result on the result port. However sometimes a bug leads to a situation where no result is ever posted back. This can be very hard to detect of ju just use a standard result/exception portset. If you're in the DSS environment you know that each operation typically has a timeout that is handled by DSS but in CCR you need to deal with this yourself. I've previously mentioned that a SmartPort could be used. But this is what it would look like with a timeout:

      1: private void DoSomething(SuccessFailurePort resultPort)
      2: {
      3:     // never posts a response.
      4: }
      5:  
      6: private void CallingSomething(TimeSpan timeout)
      7: {
      8:     var mre = new ManualResetEvent(false);
      9:     Exception error = null;
     10:     var resultPort = new SuccessFailurePort();
     11:     var timeoutPort = new Port<DateTime>();
     12:     Arbiter.Activate(
     13:         dispatcherQueue,
     14:         Arbiter.Choice(
     15:             Arbiter.Receive<SuccessResult>(false, resultPort, s => mre.Set()),
     16:             Arbiter.Receive<Exception>(
     17:                 false,
     18:                 resultPort,
     19:                 e =>
     20:                     {
     21:                         error = e;
     22:                         mre.Set();
     23:                     }),
     24:             Arbiter.Receive(
     25:                 false,
     26:                 timeoutPort,
     27:                 e =>
     28:                     {
     29:                         error = new Exception("Timeout when waiting for DoSomething");
     30:                         mre.Set();
     31:                     })));
     32:     Arbiter.Activate(
     33:         dispatcherQueue, 
     34:         Arbiter.FromHandler(() => this.DoSomething(resultPort)));
     35:     dispatcherQueue.EnqueueTimer(timeout, timeoutPort);
     36:     mre.WaitOne();
     37:     if (error != null)
     38:     {
     39:         throw error;
     40:     }
     41: }
     42:  
     43: [TestMethod]
     44: public void UsingTimeoutToDetectWhenSomethingIsNotPostingBack()
     45: {
     46:     try
     47:     {
     48:         this.CallingSomething(TimeSpan.FromSeconds(1));
     49:         Assert.Fail("Expected exception to be thrown");
     50:     }
     51:     catch (Exception e)
     52:     {
     53:         Assert.IsTrue(e.Message.Contains("Timeout"));
     54:     }
     55: }
    

    The lines to pay most attention to are lines 14 through 31.

  • Being Cellfish

    CCR tips and tricks - part 12

    • 0 Comments

    I think it's a common engineering practice to not reuse a variable for multiple things in the same method. This may be done for indexes or keeping track of errors but I think it's a bad practice. For some reason people tend to not only reuse port variables but also reuse the port instance itself. This is dangerous since the port may contain things you don't know about. Especially if timeouts are involved since what ever timed out will execute to completion anyway and post a result. Let's assume that you have a function that reads a stream and posts each line to a port like this:

      1: private void StreamReaderWithCcr(StreamReader reader, Port<string> linePort)
      2: {
      3:     for (string line = reader.ReadLine(); 
      4:         line != null; 
      5:         line = reader.ReadLine())
      6:     {
      7:         Thread.Sleep(50); //Slowing it down...
      8:         linePort.Post(line);
      9:     }
     10: }

    This method is then used in the following method:

     11: private IEnumerator<ITask> ProcessTwoFilesUnlessFirstStartsWithCowabunga(
     12:     Stream file1, 
     13:     Stream file2, 
     14:     ManualResetEvent mre)
     15: {
     16:     bool processFirstFile = false;
     17:     Port<string> linePort = new Port<string>();
     18:     Arbiter.Activate(
     19:         dispatcherQueue, 
     20:         Arbiter.FromHandler(
     21:             () => StreamReaderWithCcr(new StreamReader(file1), linePort)));
     22:     yield return linePort.Receive(
     23:         line => processFirstFile = line != "Cowabunga");
     24:     if (processFirstFile)
     25:     {
     26:         yield return linePort.Receive(line => Assert.AreEqual("12", line));
     27:     }
     28:     
     29:     Arbiter.Activate(
     30:         dispatcherQueue, 
     31:         Arbiter.FromHandler(
     32:             () => StreamReaderWithCcr(new StreamReader(file2), linePort)));
     33:     yield return linePort.Receive(line => Assert.AreEqual("21", line));
     34:     yield return linePort.Receive(line => Assert.AreEqual("22", line));
     35:     mre.Set();
     36: }

    Given those two methods this test will pass:

     37: [TestMethod]
     38: public void Processing_two_files_completely()
     39: {
     40:     var file1 = new MemoryStream(Encoding.ASCII.GetBytes("11\n12"));
     41:     var file2 = new MemoryStream(Encoding.ASCII.GetBytes("21\n22"));
     42:  
     43:     using (var testCausality = new CausalityForTests(dispatcherQueue))
     44:     {
     45:         var mre = new ManualResetEvent(false);
     46:         Arbiter.Activate(
     47:             dispatcherQueue, 
     48:             Arbiter.FromIteratorHandler(
     49:                 () => this.ProcessTwoFilesUnlessFirstStartsWithCowabunga(
     50:                     file1, 
     51:                     file2, 
     52:                     mre)));
     53:         Assert.IsTrue(
     54:             mre.WaitOne(TimeSpan.FromSeconds(5)), 
     55:             "Processing failed to complete in time");
     56:     }
     57: }

    But this test will fail on line 33 since the second line from the first file is still in the linePort:

     58: [TestMethod]
     59: public void Processing_two_files_partially()
     60: {
     61:     var file1 = new MemoryStream(Encoding.ASCII.GetBytes("Cowabunga\n12"));
     62:     var file2 = new MemoryStream(Encoding.ASCII.GetBytes("21\n22"));
     63:  
     64:     using (var testCausality = new CausalityForTests(dispatcherQueue))
     65:     {
     66:         var mre = new ManualResetEvent(false);
     67:         Arbiter.Activate(
     68:             dispatcherQueue, 
     69:             Arbiter.FromIteratorHandler(
     70:                 () => this.ProcessTwoFilesUnlessFirstStartsWithCowabunga(
     71:                     file1, 
     72:                     file2, 
     73:                     mre)));
     74:         Assert.IsTrue(
     75:             mre.WaitOne(TimeSpan.FromSeconds(5)), 
     76:             "Processing failed to complete in time");
     77:     }
     78: }

    The easy fix is to change line 28 to this and then the second test will pass:

     28: linePort = new Port<string>();

    Another way of detecting when you accidentally reuse a port is to use a method like this:

     79: private void AssertNoMoreMessages<T>(Port<T> port)
     80: {
     81:     Arbiter.Activate(
     82:         dispatcherQueue, 
     83:         Arbiter.Receive(
     84:             true, 
     85:             port, 
     86:             v => Assert.Fail("Unexpected value posted: {0}", v.ToString())));
     87: }

    And use that method on line 28.

     28: AssertNoMoreMessages(linePort);
  • Being Cellfish

    CCR tips and tricks - part 13

    • 0 Comments

    Another very common misunderstanding is that the Receive with no arguments is the same as using an empty handler. That is not true; when you yield return with the argumentless receive your method will wait for something to arrive on the port but it will not remove the item from the port. Here are two tests to illustrate the difference:

      1: private IEnumerator<ITask> SimpleDoubleEmptyReceiver(
      2:     Port<EmptyValue> port, 
      3:     ManualResetEvent mre)
      4: {
      5:     yield return port.Receive(CcrServiceBase.EmptyHandler);
      6:     yield return port.Receive(CcrServiceBase.EmptyHandler);
      7:     mre.Set();
      8: }
      9:  
     10: [TestMethod]
     11: public void UsingSimpleEmptyReceive()
     12: {
     13:     var mre = new ManualResetEvent(false);
     14:     var port = new Port<EmptyValue>();
     15:     Arbiter.Activate(
     16:         dispatcherQueue, 
     17:         Arbiter.FromIteratorHandler(
     18:             () => this.SimpleDoubleEmptyReceiver(port, mre)));
     19:     port.Post(EmptyValue.SharedInstance);
     20:     Assert.IsFalse(
     21:         mre.WaitOne(TimeSpan.FromSeconds(5)), 
     22:         "Processing completed in time");
     23: }
     24:  
     25: private IEnumerator<ITask> SimpleDoubleReceiver(
     26:     Port<EmptyValue> port, 
     27:     ManualResetEvent mre)
     28: {
     29:     yield return port.Receive();
     30:     yield return port.Receive();
     31:     mre.Set();
     32: }
     33:  
     34: [TestMethod]
     35: public void UsingSimpleReceive()
     36: {
     37:     var mre = new ManualResetEvent(false);
     38:     var port = new Port<EmptyValue>();
     39:     Arbiter.Activate(
     40:         dispatcherQueue, 
     41:         Arbiter.FromIteratorHandler(
     42:             () => this.SimpleDoubleReceiver(port, mre)));
     43:     port.Post(EmptyValue.SharedInstance);
     44:     Assert.IsTrue(
     45:         mre.WaitOne(TimeSpan.FromSeconds(5)), 
     46:         "Processing failed to complete in time");
     47: }
    

    This difference can be deceiving if you reuse a port and the purpose is to use it in a spawn and test type scenario.

  • Being Cellfish

    CCR tips and tricks - part 14

    • 0 Comments

    Sometimes you need to call an asynchronous API that is not created for CCR. While there are several types of asynchronous interfaces the pattern to do it from CCR is basically the same. Here is an example:

      1: private IEnumerator<ITask> ReadStreamWithCcr(
      2:     Stream stream, 
      3:     PortSet<string, Exception> resultPort)
      4: {
      5:     var asyncPort = new Port<IAsyncResult>();
      6:     byte[] buffer = new byte[42];
      7:     stream.BeginRead(buffer, 0, buffer.Length, asyncPort.Post, null);
      8:  
      9:     yield return asyncPort.Receive(
     10:         asyncResult =>
     11:             {
     12:                 int count = stream.EndRead(asyncResult);
     13:                 resultPort.Post(Encoding.ASCII.GetString(buffer, 0, count));
     14:             });
     15: }
    

    Note how the Port<IAsyncResult>.Post method is used as a callback since it has the same signature as the AsyncCallback. There is currently no helper in CCR that uses this pattern but you can write your own if you (like me) don't like to create ports everywhere. You need two helpers since some EndXxx methods return a value and others don't.

     16: public static PortSet<EmptyValue, Exception> DoAsyncOperation(
     17:     Action<AsyncCallback> beginAction, 
     18:     Action<IAsyncResult> endAction)
     19: {
     20:     return DoAsyncOperation<EmptyValue>(
     21:         beginAction,
     22:         result =>
     23:         {
     24:             endAction(result);
     25:             return EmptyValue.SharedInstance;
     26:         });
     27: }
     28:  
     29: public static PortSet<T, Exception> DoAsyncOperation<T>(
     30:     Action<AsyncCallback> beginAction, 
     31:     Func<IAsyncResult, T> endAction)
     32: {
     33:     var resultPort = new PortSet<T, Exception>();
     34:     try
     35:     {
     36:         beginAction(result =>
     37:         {
     38:             try
     39:             {
     40:                 var r = endAction(result);
     41:                 resultPort.Post(r);
     42:             }
     43:             catch (Exception e)
     44:             {
     45:                 resultPort.Post(e);
     46:             }
     47:         });
     48:     }
     49:     catch (Exception e)
     50:     {
     51:         resultPort.Post(e);
     52:     }
     53:  
     54:     return resultPort;
     55: }
    

    With that helper the original method looks like this:

     58: public IEnumerator<ITask> ReadStreamWithCcr2(
     56:     Stream stream, 
     57:     PortSet<string, Exception> resultPort)
     58: {
     59:     byte[] buffer = new byte[42];
     60:     var readPort = DoAsyncOperation<int>(
     61:             cb => stream.BeginRead(buffer, 0, buffer.Length, cb, null),
     62:             stream.EndRead);
     63:     yield return readPort.Choice(
     64:         c => resultPort.Post(Encoding.ASCII.GetString(buffer, 0, c)), 
     65:         resultPort.Post);
     66: }
    

    Looks very similar doesn't it. But there is a big difference! BeginXxx and EndXxx methods may throw exceptions and the first implementation I showed you does not handle that. So probably you want to add some try-catch-blocks (unless you purely rely on causalities to handle errors) which makes the first implementation more cumbersome to work with. And you'll probably forget to add those try-blocks once in a while...

  • Being Cellfish

    CCR tips and tricks - part 17

    • 0 Comments

    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.
Page 1 of 2 (17 items) 12