Udostępnij za pośrednictwem


CCR tips and tricks - part 14

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...