Udostępnij za pośrednictwem


CCR tips and tricks - part 12

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