C# 同步对象和计时器.Timer 线程

5

你好,我正在尝试让Timer.Timer类的Elapsed事件在我的主线程上触发。 我只能使用VS 2008,.net 3.5…… 在这篇帖子中: Do C# Timers elapse on a separate thread? 其中提到使用SynchronizingObject将使处理程序在拥有该对象的线程上执行。

所以我尝试了这个:

class MyTimer
{
  private readonly Timer timer;

  public MyTimer(ISynchronizeInvoke synchronizingObject)
  {
    Console.Out.WriteLine(Thread.CurrentThread.ManagedThreadId);
    timer = new Timer(1000);
    timer.SynchronizingObject = synchronizingObject;
    timer.Elapsed +=
      delegate
        {
          timer.Stop();
          Thread.Sleep(2000);
          Console.Out.WriteLine(Thread.CurrentThread.ManagedThreadId);
          timer.Start();

        };
    timer.Start();
  }
}

class Program
{
  static void Main(string[] args)
  {
    ISynchronizeInvoke syncObject = new Control();
    var mytimer = new MyTimer(syncObject);

    Console.ReadKey();
  }
}

但是输出结果为:1,4,4,4,4...

为什么会这样呢?我该如何使Elapsed手柄在主线程上执行。

我尝试使用事件的同步对象,但也没能解决问题。

  public static ElapsedEventHandler Wrap(ElapsedEventHandler original, ISynchronizeInvoke synchronizingObject) 
  {
    return (sender, args) =>
    {
      if (synchronizingObject.InvokeRequired)
      {
        synchronizingObject.Invoke(original, new object[] { sender, args });
      }
      else
      {
        original(sender, args);
      }
    };
  }

并且:

timer.Elapsed += Wrap(
      delegate
        {
          timer.Stop();
          Thread.Sleep(200);
          Console.Out.WriteLine(Thread.CurrentThread.ManagedThreadId);
          timer.Start();
        }, 
        synchronizingObject);

但是仍然没有成功......每次调用InvokeRequired都返回false......
强制进行调试时,会引发无效操作:"在控件的窗口句柄创建之前,无法调用Invoke或BeginInvoke。"
最后的办法是看看:http://www.codeproject.com/Articles/12082/A-DelegateQueue-Class?msg=3655119#xx3655119xx,但这真的有必要吗?还是有更简单的解决方案吗?
2个回答

4
提出的答案并没有回答原始问题,即为什么System.Timers.Timer Elapsed事件在主线程上无法正确触发,即使SynchronizingObject已正确设置为来自主线程的控件。
我自己也遇到了这个问题,解决方案是由于即使已经正确构造,该控件仍然没有句柄。诀窍是将控件的Handle属性值放入一个虚拟变量中,以便初始化它(请参见http://blogs.msdn.com/b/jfoscoding/archive/2004/11/24/269416.aspx)。
但是,一旦解决了这个问题,就会在原始帖子的代码中创建新问题,因为Console.ReadKey()正在阻塞,但Elapsed处理程序需要在该线程上运行。一种解决方法是执行Application.DoEvents(),尽管可能有更好的解决方法。底线:在同步回主线程时,请不要阻塞它!
以下是帖子作者的原始代码和修改后的工作代码:
class MyTimer {
    private readonly System.Timers.Timer timer;
    private static AutoResetEvent elapsedOutputted = new AutoResetEvent(false);
    //private static AutoResetEvent mainReady = new AutoResetEvent(true);

    public MyTimer(ISynchronizeInvoke synchronizingObject) {

        Console.Out.WriteLine(Thread.CurrentThread.ManagedThreadId);
        timer = new System.Timers.Timer(1000);
        timer.SynchronizingObject = synchronizingObject;
        timer.Elapsed += timer_Elapsed;
        timer.Start();
    }

    void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) {
        //timer.Stop(); not needed
        Console.Out.WriteLine(Thread.CurrentThread.ManagedThreadId);
        elapsedOutputted.Set();
        //Thread.Sleep(2000); not needed
        //timer.Start(); not needed
    }

    static void Main(string[] args) {
        Control c = new Control();
        IntPtr tempNotUsed = c.Handle;
        var mytimer = new MyTimer(c);

        for (int runs = 0; runs < 10; runs++) {
            while (!elapsedOutputted.WaitOne(1000)) { //this will deadlock, but the 1000ms timeout will free it
                Application.DoEvents(); //not sure if DoEvents is the best idea, but it does the trick
            } //end while
        } //end for
    } //end Main
} //end class

1
你是正确的,Application.DoEvents 不是最好的选择。更好的方法是使用 Application.Run() 来启动消息泵。然后,您需要调用 Application.Exit() 来“关闭”主线程。然而,在我看来,OP 的 Dispatcher 解决方案更好,因为它不会让您依赖 Winforms。 - Scott Chamberlain

2

我找到了一种使用DispatcherTimer和Dispatcher.Run()在新线程中启动消息泵的解决方案。为了平稳地停止Dispatcher并退出服务,我使用了

if (_currentDispatcher != null) _currentDispatcher.InvokeShutdown();
_thread.Join();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接