0

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.)
Jeff Putz
  • 14,504
  • 11
  • 41
  • 52
  • 2
    Windows has a timer resolution of 15.625ms. If you're setting your resolution to 25ms then you're going to get some lumpy firing of the events. Some in 15.625ms and some in 31.25ms. You just can't actually get 25ms intervals. There is also nothing stopping events being called simultaneously if they use separate threads. – Enigmativity Jun 29 '23 at 23:54
  • 1
    "since event handlers execute serially" - no, they don't. Threads run serially, but event handlers can be called by multiple threads. – Enigmativity Jun 29 '23 at 23:56
  • I'm not using Windows. It happens to be running on a Mac, but honestly it could be Windows or Linux as well. Because .Net. And as I indicated, there's only one thread calling the event. – Jeff Putz Jun 30 '23 at 01:46
  • Here's text from the documentation: "If the SynchronizingObject property is Nothing, the Elapsed event is raised on a ThreadPool thread. If processing of the Elapsed event lasts longer than Interval, the event might be raised again on another ThreadPool thread. In this situation, the event handler should be reentrant." – Enigmativity Jun 30 '23 at 03:55
  • Your event can be raised on multiple threads if you're using `System.Timers.Timer`. – Enigmativity Jun 30 '23 at 03:56
  • @ Enigmativity I've read the documentation. If I understood the optimal way to proceed based on that, I wouldn't be asking the questions here. – Jeff Putz Jun 30 '23 at 04:43
  • 1
    You're sounding a bit rude here. I'm just trying to help. You stated in your question "since event handlers execute serially", but the documentation shows that that is not necessarily true. It sounds like to me that you simply need to put in a `lock` and see if that resolves the issue. – Enigmativity Jun 30 '23 at 05:27
  • C# may not be the right language for this project. Have you considered using a different programming language? – Tu deschizi eu inchid Jun 30 '23 at 14:18
  • @Tudeschizieuinchid that's a completely unqualified opinion. Not right according to whom? I've tried some of the suggestions of the person who gave an answer, and they generally work. – Jeff Putz Jul 02 '23 at 19:58
  • @JeffPutz: It wasn't an opinion of whether or not it is a good choice, but rather a comment that implies that one shouldn't tie oneself to a particular computer programming language without first identifying the needs of the project and then determining which language(s) may be best suited for the project. – Tu deschizi eu inchid Jul 02 '23 at 22:21
  • @Tudeschizieuinchid feels like you're trolling, or at the very least, not seeking to help find an answer to the question. C#/.Net can do pretty much anything, which is why, among other things, it has one of the fastest web servers (Kestrel) and 1 in 4 games uses C# (Unity). – Jeff Putz Jul 02 '23 at 22:42
  • @JeffPutz: Trolling? I'm not sure what that is. _1 in 4 games uses C# (Unity)_: If this true, that means that 75% don't use C# (Unity). For many things C#, seems to be good enough. Do you happen to work for Microsoft? – Tu deschizi eu inchid Jul 02 '23 at 23:49

1 Answers1

2

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.

JonasH
  • 28,608
  • 2
  • 10
  • 23
  • Of course there's yet another timer introduced in a recent version! I didn't know about that one. I'm not running on Windows, so the multimedia thing is out. Looks like periodic or the Stopwatch approach will be key. Handler order doesn't matter, so I'll try your parallel suggestion. Concurrency also is not critical. I did try timers for each thing, but it used more threads and appeared to increase CPU usage. I'll report back! – Jeff Putz Jun 30 '23 at 13:39
  • 1
    @JeffPutz I would suspect that all desktop OSes have some way to increase timer resolution. After all, high frequency hardware timers has been standard for a decade. And using a high resolution should greatly simplify things like video playback. – JonasH Jun 30 '23 at 14:03
  • Definitely marked this as the answer. The parallel even handler execution actually results in *lower* CPU usage, though I suspect that's just the fidelity of Activity Monitor. PeriodicTimer works as expected, but in this case it made it more obvious that I need to take a new approach where the timer and event delegates need to know how to account for missed intervals (a good thing). @jonash thank you for the help! – Jeff Putz Jul 02 '23 at 22:35