14

长话短说,我需要.Net 中的精确计时器 - 精度以毫秒为单位 - 意思是,如果我告诉它在 10 毫秒过去后触发事件,它必须这样做,+-1 毫秒。内置的 .Net Timer 类的精度似乎为 +-16ms,这对我的应用程序来说是不可接受的。

我发现这篇文章http://www.codeproject.com/Articles/98346/Microsecond-and-Millisecond-NET-Timer为我需要的计时器提供了一个代码(甚至更多 - 以微秒为单位) .

但是,问题是,等效的 OnTimer 似乎在另一个线程中执行。所以,如果我添加一些代码,说:

label1.Text = "Hello World";

我会得到一个例外,因此我将不得不像这样实际编写它:

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

据我了解,这是因为 OnTimer 事件是从计时器的线程中触发的——在该线程中,时间已经过去了,直到足够的时间超过了 Interval,然后下一个 OnTimer 事件被触发。.Net Timer没有这样的问题——在.Net Timer的OnTimer中,我可以自由修改控件的成员。

问题:我应该更改什么以便我的计时器在主线程中运行它的 OnTimer 事件?添加“调用”是唯一的选择吗?

4

3 回答 3

16

虽然有几种解决方法,但我通常更喜欢的一种是让计时器捕获SynchronizationContext.Current它创建时的值。当在 UI 线程中时,该值将包含当前同步​​上下文,当在 UI 上下文中时,该值可用于执行消息循环中的方法。这将适用于 winforms、WPF、silverlight 等。所有这些范例都设置了同步上下文。

只需在创建计时器时获取该值,假设它是在 UI 线程中创建的。如果您希望有一个可选的构造函数/属性来设置该值,以便即使未在 UI 线程中创建计时器也可以使用它,尽管大多数时候不需要这样做。

然后只需使用该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
}
于 2013-10-21T15:08:56.423 回答
3

没有办法将 UI 元素访问调度到主线程。如果更新 UI 元素确实是您打算在计时器回调中做的唯一事情,那么请忘记您的计时器精度要求。用户不会看到 16ms 和 50ms 之间的差异。

否则在计时器回调中执行时间关键的工作并将其余的 UI 工作分派到主线程:

void OnTimer()
{
  // time critical stuff here

  Invoke( new MethodInvoker(() =>{label1.Text = "Hello World";}));
}
于 2013-10-21T15:08:53.530 回答
2

在 wpf 中,您可以使用调度程序类在 UI 线程中调度消息:

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

在winforms中需要调用invoke方法:

this.Invoke(()=> label1.Text = "Hello World");
于 2013-10-21T15:09:37.717 回答