【问题标题】:C# SynchronizingObject and Timer.Timer threadC# SynchronizingObject 和 Timer.Timer 线程
【发布时间】:2012-10-08 12:50:29
【问题描述】:

您好,我正在尝试在我的主线程上触发 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 处理程序在主线程上执行。

我尝试使用SynchronizingObject for an event 但这也无济于事:

  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 为假...

在调用中强制调试会导致无效操作:“在创建窗口句柄之前,不能对控件调用 Invoke 或 BeginInvoke。”

最后的手段是调查: http://www.codeproject.com/Articles/12082/A-DelegateQueue-Class?msg=3655119#xx3655119xx 但这真的有必要吗?还是有一些更简单的解决方案?

【问题讨论】:

    标签: c# .net multithreading timer deadlock


    【解决方案1】:

    建议的答案没有回答原始问题,这就是为什么 System.Timers.Timer Elapsed 事件没有在主线程上正确触发的原因,即使 SynchronizingObject 已正确设置为来自主线程的控件。

    我自己也遇到过这个问题,结果发现解决方案是由于 Control 还没有句柄,即使它已正确构造。诀窍是将控件的 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
    

    【讨论】:

    • 你是正确的 Application.DoEvents 不是最好的主意。更好的做法是使用Application.Run() 启动消息泵。然后你需要调用Application.Exit() 来“关闭”主线程。但是,在我看来,OP 的 Dispatcher 解决方案是更好的解决方案,因为它不会让您拥有 Winforms 依赖项。
    【解决方案2】:

    我找到了使用 DispatcherTimer 的解决方案 和 Dispatcher.Run() 在新线程中启动消息泵。为了停止调度程序并顺利退出服务,我使用了

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

    【讨论】:

      猜你喜欢
      • 2020-04-07
      • 1970-01-01
      • 2013-03-01
      • 2015-04-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多