Wednesday, June 26, 2013

C#, Async Glue programming with CallReactor class

Our developers ask me: What is the "reactor"? Plainly: a reactor is a mechanism to breath-in execution flow into some object graph for asynchronous execution, usually a callback/event dispatcher. In other words - it is at least one thread of execution that REACTS to certain condition/change in data (such as property change or collection change). A classical use of reactors are event loops, i.e. the one used by the UI thread that monitors event queue and fires events (callbacks) appropriately. Reactors are ideal for multi-threaded programming when otherwise disjoint parties need to play in concert. The NFX.Glue.CallReactor class facilitates asynchronous call dispatching and provides callback mechanism upon result receipt from the server. Every instance has a dedicated thread that monitors all Call instances in the reactor. Here is how it works:
  • Call Async_* version of Glue client proxy call. It returns CallSlot instance
  • A call to Async_* method is wrapped in the new Call{} construct that remembers it in the reactor scope
  • Pass a lambda function that gets called back by the reactor
  • Optionally, the reactor .ctor takes a callback that is fired when all Calls registered with the reactor are done
The code below is taken from the WinForms-based manual test case,hence "Invoke(delegate)" to sync the callback with the UI thread.
var client1 = new MarazmContractClient(cbo.Text);
var client2 = new MarazmContractClient(cbo.Text);

    new CallReactor(false,
        finishedReactor => 
        {
            client1.Dispose(); 
            client2.Dispose();
            Invoke( (Action)( () =>
                       MessageBox.Show(finishedReactor.Context.ToString())
                        ) );
        },
        string.Empty,
        new Call( client1.Async_Echo("One"),
         (reactor, call) => reactor.Context =
           ((string)reactor.Context) + 
            call.CallSlot.GetValue()),
        new Call( client2.Async_Echo("Two"), 
         (reactor, call) => reactor.Context =
           ((string)reactor.Context) +
            call.CallSlot.GetValue()),
        new Call( client1.Async_Echo("Three"),
         (reactor, call) => reactor.Context = 
           ((string)reactor.Context) +
            call.CallSlot.GetValue())
    );