Search code examples
c#timerstate-machineevent-driven

C# Event driven state machine needs to release event raiser using Windows.Forms.Timer


In our legacy VB6 code we used a system timer to perform a callback so that state machines could be run without blocking event handlers. Some Psuedo code example

Sub Unhooked(State info)
    Select Case info
        Case 1
            NextState = somestate1
        Case 2
            NextState = somestate2
        Case 3
            NextState = somestate3
    End Select
    RunStateMachine
End Sub

Sub RunStateMachine()
    MyObject.GoDoSomethingAndCallMeBack
End Sub

Sub MyObject_EventCallback(State info)
    APITimer.SetUpCallBackTarget (Unhooked, info)
    APITimer.CallMeBackInASec
End Sub

Hopefully you get the idea where some call is made in the state machine that will result later on in the event handler being fired and it sets up a timer to do a callback so that the event handler code can complete before we move on to the next state.

I am continuing a similar vein in C# but it feels wrong as I'm sure the language would provide a cuter way of doing this. Since my C# state machine is still calling VB6 objects that raise events on the UI thread, is there a better way of 'unhooking' the event handler so that it is released before the state machine continues on it's way?

I thought I could use BeginInvoke to add a message to the pump to run the state machine but on the same thread instead of a thread pool thread but my class isn't a form or control. I think I could come up with a solution that doesn't use timers but, well they are dead simple to use for this so any ideas would be great.


Solution

  • To solve your immediate problem, you could queue a Task to the UI thread context, e.g.:

    var ui = TaskScheduler.FromCurrentSynchronizationContext();
    Task.Factory.StartNew(() => Unhooked(info), CancellationToken.None,
        TaskCreationOptions.None, ui);
    

    The UI SynchronizationContext can be detected even though your object is not a form or control. For more information on SynchronizationContext, see my MSDN article.

    If you have this in your code a lot, you'll probably want to wrap it up into a helper method:

    public void CallbackLater(Action action)
    {
      Task.Factory.StartNew(action, CancellationToken.None,
        TaskCreationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
    }
    
    void MyObject_EventCallback(State info)
    {
      CallbackLater(() => Unhooked(info));
    }
    

    In the broader scheme of things, seriously consider the Async CTP and designing a Task-Based Asynchronous Pattern API instead of having the events raised on the UI thread (i.e., the Event-Based Asynchronous Pattern).