How to make a C# timer that fires events in the main thread?

14,480

Solution 1

While there are several ways of going about it, the one that I would generally prefer is to have the timer capture the value of SynchronizationContext.Current when it is created. That value will, when in the UI thread, contain the current synchronization context which can be used to execute methods in the message loop when in a UI context. This will work for winforms, WPF, silverlight, etc. All of those paradigms set a synchronization context.

Just grab that value when the timer is created, assuming it's created in the UI thread. If you want have an optional constructor/property to set the value so that you can use it even if the timer isn't created in the UI thread you can, although that shouldn't be needed most of the time.

Then just use the Send method of that context to fire the event:

public class Timer
{
    private SynchronizationContext syncContext;
    public Timer()
    {
        syncContext = SynchronizationContext.Current;
    }

    public event EventHandler Tick;

    private void OnTick()
    {
        syncContext.Send(state =>
        {
            if (Tick != null)
                Tick(this, EventArgs.Empty);
        }, null);
    }

    //TODO other stuff to actually fire the tick event
}

Solution 2

There's no way around dispatching UI Element access to the main thread. If updating a UI element is really the only thing that you intend to do in the timer callback then forget about about your timer precision requirement. The user won't see the difference between 16ms and 50ms.

Otherwise carry out the time critical work in your timer callback and dispatch the rest of the UI work to the main thread:

void OnTimer()
{
  // time critical stuff here

  Invoke( new MethodInvoker(() =>{label1.Text = "Hello World";}));
}

Solution 3

In wpf you can use the dispatcher class to dispatch messages in the UI thread:

Dispatcher.CurrentDispatcher.BeginInvoke(
      new Action(()=> label1.Text = "Hello World"));

In winforms you need to call the invoke method:

this.Invoke(()=> label1.Text = "Hello World");
Share:
14,480

Related videos on Youtube

Istrebitel
Author by

Istrebitel

Updated on September 15, 2022

Comments

  • Istrebitel
    Istrebitel almost 2 years

    Long story short, I need a precise timer in .Net - with prescision in milliseconds - meaning, if I tell it to fire an event when 10ms passes, it must do so, +-1ms. The built-in .Net Timer class has a precision of +-16ms it seems, which is unacceptable for my application.

    I found this article http://www.codeproject.com/Articles/98346/Microsecond-and-Millisecond-NET-Timer which provides a code for a timer that is exactly what I need (even more - that has precision in microseconds).

    However, problem is, the OnTimer equivalent seems to be executed in another thread. So, if I add some code that does, say:

    label1.Text = "Hello World";
    

    I will get an exception, and thus I will have to actually write it like this:

    Invoke( new MethodInvoker(() =>{label1.Text = "Hello World";}));
    

    This is, from what I understand, because the OnTimer event is fired from the timer's thread - where time is passed until enough has passed to be over the Interval, and then next OnTimer event is fired. The .Net Timer does not have such a problem - in OnTimer of the .Net Timer, I can freely modify controls's members.

    Question: What should I change so that my timer will run it's OnTimer event in the main thread? Is adding "Invoke" the only choice?

    • spender
      spender over 10 years
      The only reason the Forms timer does not suffer this is because it Invokes on your behalf as a convenience. If you use other timers, you need to Invoke as you describe in your question.
    • spender
      spender over 10 years
      It's probably worth remembering that Windows is not a real-time operating system. Trying to make anything happen at 1ms accuracy can be tricky.
    • Feidex
      Feidex over 10 years
      I wonder why you need a precision of 1 millisecond for changing a label if your display has a latency of several milliseconds.
  • Eli Arbel
    Eli Arbel over 10 years
    I believe Post would be better. With Send you're wasting time on much unneeded synchronization. Still, the windows forms timer would yield better results since it doesn't require synchronization at all - it directly sends a message to the window instead of launching thread-pool threads.
  • Servy
    Servy over 10 years
    @EliArbel Whether to use Send or Post is about whether you want the timer to wait for the handlers to all complete before firing more, or not. As for using the other timer; the OP has specifically stated that it's not an option for him in this context, and that he needs to use his own timer that then marshals to the UI thread. This does exactly that.
  • Feidex
    Feidex over 10 years
    Will this execute the Tick handler in the main thread in 10 millisecond intervals and +/- 1 millisecond accuracy, or do only the syncContext.Send calls meet the requirements?
  • Eli Arbel
    Eli Arbel over 10 years
    My point is that it would most definitely be slower than using the Windows Forms timer, since the bottleneck here is the window's message pump, not the timer.
  • Servy
    Servy over 10 years
    @dtb Well obviously the method won't actually get executed until the message pump gets to it. If the queue is empty this should be as fast as anything else that marshals to the UI thread, which admittedly may not be fast enough. I don't see how any other approach would ever be faster though.
  • Servy
    Servy over 10 years
    @EliArbel This has the potential of adding the item to the windows message pump more quickly than the other timer if it fires based on a tick event with more precision. Whether or not the queue is in a position to handle it quickly enough is an issue that both timers would have, and may or may not be a problem for the OP. Regardless, this answers the question that was asked. If he says it doesn't work, then odds are nothing will. If it does, then good for him.
  • Istrebitel
    Istrebitel over 10 years
    Yes I kinda understood, in process of writing the code, that even at 120Hz monitor we still have a GUI precision of 8ms, since we can only change frames each 8ms. So there is no much point to fight for precision in the UI part. However it's not only about UI, so I will still have a use for a precise timer.
  • Oliver Weichhold
    Oliver Weichhold over 10 years
    @Istrebitel That's why I mentioned that in my post :-)