Search code examples
c#.netmultithreadingalgorithmtimer

C# - Can event handlers be executed in parallel? And within a timer?


Background: I'm exploring a solution to control automated (theatrical/concert) lighting. If you're unfamiliar with the DMX protocol, basically it sends 512 bytes over the wire, about 40-ish times per second, and the receiving light fixtures that are mapped to certain bytes (channels) set themselves accordingly. So if a light has its dimmer on channel 7, and the value coming is 255, it's fully on, and if it's 0, it's dark. There are a few intermediate network protocols in between computers and a DMX interface (primarily Art-Net and sACN if you're super curious), so the code I'm writing is just spraying the network with packets formatted for one of the network protocols every 25ms (40 times per second).

The sending of the bytes is easy enough, and surprisingly there isn't a lot of overhead in packaging the byte arrays. However, a number of things can be setting values on those arrays, like manual inputs ("faders" on screen, if you will), or algorithms intended to fade between lights or make them move or whatever. I have different ideas about how to prioritize those and which thing "wins" for the periodic refresh. That part is less important than how to make all of those things execute on the refresh. None of them individually take more than a millisecond or two, but they all need to finish before the next refresh.

In terms of code, there's a timer (the System.Timers variety) that looks like this:

_sendingTimer = new System.Timers.Timer();
_sendingTimer.Interval = _networkInterface.SendingInterval; // 25ms
_sendingTimer.AutoReset = false;
_sendingTimer.Elapsed += async (_, _) =>
{
    SendTheData(); // Takes on average a ms or two

    _stateManager.OnUpdateCycle();
    
    _sendingTimer.Start();
};
_sendingTimer.Start();

The _stateManager bit looks like this:

public void OnUpdateCycle()
{
    UpdateCycled.Invoke(this, EventArgs.Empty); // Can these be run in parallel?
}
public event EventHandler UpdateCycled;

There could potentially be a bunch of things attached to that event, which, as I said, mostly do math like figuring out in a cross-fade between cues where the dimmer should be set relative to where it was in the last cycle. These actions are pretty light weight, maybe running as long as 2ms if I have debugging attached or some logging. My concern though is that if I have 25 of them running, they won't all complete, since event handlers execute serially.

Questions:

  • Is this timer strategy the right set up so that it runs every interval with some reasonable (if imperfect) accuracy, or will long execution of the methods delay the next interval? I think ideally that if for some reason it did not complete by the end of the interval, I would want it to just abandon the previous attempt.
  • Is there an efficient way to make the event handlers execute in parallel, and using whatever resources are necessary to complete? Oddly enough, the network protocols are sent as UDP packets, so if they get lost, whatever, the lights will respond to the next one. But if these calculations on the event handler don't complete, the gaps could span multiple cycles, making fading or movement not smooth. Some follow sine curves, so you would definitely see it. (Assume that the values I'm computing are relative to time, and not the previous values.)

Solution

  • Is this timer strategy the right set up so that it runs every interval with some reasonable (if imperfect) accuracy, or will long execution of the methods delay the next interval? I think ideally that if for some reason it did not complete by the end of the interval, I would want it to just abandon the previous attempt.

    The effective timer interval will be affected by the execution time of your event handler. The default windows timer resolution will also add a large amount of quantitation noise to your times. There are a few things you could do:

    • Measure the time using a stopwatch, and compute the remaining time until the next cycle. You may want to use a Threading.Timer instead to change the next pending time.
    • Use a Periodic Timer in a loop, awaiting WaitForNextTickAsync
    • Increase the windows timer frequency
    • Use a multimedia timer ( I think this also just increases the timer frequency, but I might be wrong)
    • A spinwait until the next period (note, will consume a cpu core and burn a bunch of extra power)

    Is there an efficient way to make the event handlers execute in parallel, and using whatever resources are necessary to complete?

    You could try something like this:

    private void RaiseParallel()
    {
        var ev = myEvent;
        if (ev != null)
        {
            Parallel.ForEach(ev.GetInvocationList(),
                 e =>
                {
                    ((EventHandler)e)(this, EventArgs.Empty);
                });
        }
    }
    public event EventHandler myEvent;
    

    But I would really not recommend it since event handlers are expected to be executed sequentially. If you are doing something special I would suggest making this more apparent, like maintaining a list of delegates explicitly, with an explicit Add method. Or even better, a IParallelUpdate-interface, with a Dependency injection container to resolve all components implementing this.

    Having said that, concurrency would be the last solution I would turn to. The first would be to profile your code and see if you can decrease the time to be well under your period. Perhaps only some things need to run concurrently? Does these things need to be synchronized with the timer period? or can they just keep some internal timer or process to do any slow computations every now and then?

    Whatever you do, make it clear for everyone involved if there is concurrency involved. And make really sure your code is thread safe. If you are not confident about what is needed to make your code thread safe, do not write any multi threaded code.