简体   繁体   English

如何制作一个C#计时器来触发主线程中的事件?

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

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. 简而言之,我需要.Net中的精确计时器 - 精确到毫秒 - 这意味着,如果我告诉它在10ms通过时发射事件,它必须这样做,+ -1ms。 The built-in .Net Timer class has a precision of +-16ms it seems, which is unacceptable for my application. 内置的.Net Timer类似乎具有+ -16ms的精度,这对我的应用来说是不可接受的。

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). 我发现这篇文章http://www.codeproject.com/Articles/98346/Microsecond-and-Millisecond-NET-Timer ,它提供了一个完全符合我需要的计时器代码(甚至更多 - 具有精确度,以微秒为单位) 。

However, problem is, the OnTimer equivalent seems to be executed in another thread. 但问题是,OnTimer等效似乎是在另一个线程中执行的。 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. 根据我的理解,这是因为OnTimer事件是从计时器的线程触发的 - 时间过去直到有足够的时间超过Interval,然后触发下一个OnTimer事件。 The .Net Timer does not have such a problem - in OnTimer of the .Net Timer, I can freely modify controls's members. .Net Timer没有这样的问题 - 在.Net Timer的OnTimer中,我可以自由地修改控件的成员。

Question: What should I change so that my timer will run it's OnTimer event in the main thread? 问题:我应该更改什么,以便我的计时器将在主线程中运行它的OnTimer事件? Is adding "Invoke" the only choice? 添加“Invoke”是唯一的选择吗?

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. 虽然有几种方法可以实现,但我通常更喜欢的是让计时器在创建时捕获SynchronizationContext.Current的值。 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. 当在UI线程中时,该值将包含当前同步​​上下文,该上下文可用于在UI上下文中执行消息循环中的方法。 This will work for winforms, WPF, silverlight, etc. All of those paradigms set a synchronization context. 这适用于winforms,WPF,silverlight等。所有这些范例都设置了同步上下文。

Just grab that value when the timer is created, assuming it's created in the UI thread. 只要在创建计时器时获取该值,假设它是在UI线程中创建的。 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. 如果你想要一个可选的构造函数/属性来设置值,这样你就可以使用它,即使你没有在UI线程中创建计时器,尽管大多数时候都不需要。

Then just use the Send method of that context to fire the event: 然后只需使用该上下文的Send方法来触发事件:

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
}

There's no way around dispatching UI Element access to the main thread. 无法将UI Element访问权限分配给主线程。 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. 如果更新UI元素实际上是您打算在计时器回调中做的唯一事情,那么就忘记了您的计时器精度要求。 The user won't see the difference between 16ms and 50ms. 用户将看不到16ms和50ms之间的差异。

Otherwise carry out the time critical work in your timer callback and dispatch the rest of the UI work to the main thread: 否则,在计时器回调中执行时间关键工作,并将其余的UI工作分派给主线程:

void OnTimer()
{
  // time critical stuff here

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

In wpf you can use the dispatcher class to dispatch messages in the UI thread: 在wpf中,您可以使用调度程序类在UI线程中调度消息:

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

In winforms you need to call the invoke method: 在winforms中,您需要调用invoke方法:

this.Invoke(()=> label1.Text = "Hello World");

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM