有没有办法强制 Win32 计时器在不空闲时执行?

Posted

技术标签:

【中文标题】有没有办法强制 Win32 计时器在不空闲时执行?【英文标题】:Is there a way to force a Win32 Timer to execute when not idle? 【发布时间】:2018-03-26 14:43:49 【问题描述】:

使用 SetTimer 创建的 Win32 计时器通常仅在消息队列为空时执行。如果 GUI 线程非常繁忙因此不为空,是否有任何方法可以手动强制执行?

(编辑) 正如下面所讨论的,这尤其是关于让低优先级消息(在这种情况下间接显示工具提示)在 UI 线程饱和时继续工作(但不阻止它)。这是一些代码:

using System;
using System.Threading;
using System.Windows.Forms;

namespace ToolTipTesting

   public partial class Form1 : Form
   
      Thread _thread = null;
      bool _run = false;
      bool _exit = false;

      public Form1()
      
         var tsbStart = new ToolStripButton();
         tsbStart.Text = "Start";
         tsbStart.Click += (s,e) => _run = true;

         var tsbStop  = new ToolStripButton();
         tsbStop.Text = "Stop";
         tsbStop.Click += (s,e) => _run = false;

         var tslValue = new ToolStripLabel();

         var ts = new ToolStrip();

         ts.Items.Add(tsbStart);
         ts.Items.Add(tsbStop);
         ts.Items.Add(tslValue);

         Controls.Add(ts);

         _thread = new Thread(() =>
         
            int i = 0;

            while (!_exit)
            
               if(_run)
                
                  var result = BeginInvoke(new Action(() =>  tslValue.Text = (i++).ToString(); ts.Update();  ));

                  while(!_exit && !result.IsCompleted)
                     result.AsyncWaitHandle.WaitOne(10);
               
               else
               
                  Thread.Sleep(100);
               
            
         );

         FormClosing += (s,e) =>
          
            _exit = true;
            _thread.Join();
         ;

         _thread.Start();
      
   

如果这是“错误的做法”……很高兴听到“正确的做法”。

【问题讨论】:

如果您不喜欢 SetTimer 的工作方式,请使用 SetTimer 以外的其他工具。 嘿雷蒙德!在这种情况下,我们删除了几个级别...我们在 ToolStripButton 上设置了一个工具提示,但发现当我们的 gui 线程忙时,工具提示停止工作。我们跟踪到 WM_MOUSEHOVER,它没有被生成。然后我们知道 TrackMouseEvent 在下面创建了一个计时器。所以,我们可以到处滚动我们自己的工具提示逻辑,但这是不可取的。 如果GUI线程忙,它也不会响应输入(因为输入消息也是低优先级的),所以它无法知道是否关闭,所以强制定时器跑步无济于事。 (而且如果 UI 线程很忙,它无论如何也不能做任何 UI 工作。)解决办法是停止挂起 UI 线程。 我们有一个辅助线程,它使用 EndInvoke(BeginInvoke(...)) 将要做的事情排队。所以我们不会锁定 UI 线程,而是调用足够快的速度以使队列永远不会为空(或者足够长以处理低优先级消息)。它仍然响应鼠标消息(例如,当您输入时 ts 按钮会改变颜色),但低优先级的消息会丢失。如果我们节流,那么事情就会开始正常工作,但是节流时间取决于工作负载,并且似乎不理想,因为它全局会使事情变慢。对于 WM_PAINT,我们有 UpdateWindow。但是对于 WM_TIMER 我不知道。 顺便说一句,我自言自语地把它翻译成一篇 The Old New Thing 文章“首先客户问这个......但是当被问到真的很关心这个时,当我们深入挖掘时,我们得到了关键细节。” :) 【参考方案1】:

成功!正如 Raymond Chen 上面所建议的,解决方案是使用 PeekMessage。这是完整的代码:

using System;
using System.Threading;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace ToolTipTesting

   [StructLayout(LayoutKind.Sequential)]
   public struct NativeMessage
   
      public IntPtr handle;
      public uint msg;
      public IntPtr wParam;
      public IntPtr lParam;
      public uint time;
      public System.Drawing.Point p;
   

   public partial class Form1 : Form
   
      [DllImport("user32.dll")]
      [return: MarshalAs(UnmanagedType.Bool)]
      static extern bool PeekMessage(ref NativeMessage lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg);

      const int QS_TIMER = 0x0010;
      const int WM_TIMER = 0x0113;

      Thread _thread = null;
      bool _run = false;
      bool _exit = false;

      public Form1()
      
         var tsbStart = new ToolStripButton();
         tsbStart.Text = "Start";
         tsbStart.Click += (s,e) => _run = true;

         var tsbStop  = new ToolStripButton();
         tsbStop.Text = "Stop";
         tsbStop.Click += (s,e) => _run = false;

         var tslValue = new ToolStripLabel();

         var ts = new ToolStrip();

         ts.Items.Add(tsbStart);
         ts.Items.Add(tsbStop);
         ts.Items.Add(tslValue);

         Controls.Add(ts);

         _thread = new Thread(() =>
         
            int i = 0;

            NativeMessage nativeMessage = new NativeMessage();

            while (!_exit)
            
               if(_run)
                
                  var result = 
                        BeginInvoke
                        (
                           new Action
                           (
                              () => 
                              
                                 tslValue.Text = (i++).ToString();

                                 PeekMessage
                                 (
                                    ref nativeMessage,
                                    IntPtr.Zero,
                                    WM_TIMER,
                                    WM_TIMER,
                                    QS_TIMER << 16
                                 );

                                 ts.Update();
                               
                           )
                        );

                  while(!_exit && !result.IsCompleted)
                     result.AsyncWaitHandle.WaitOne(10);
               
               else
               
                  Thread.Sleep(100);
               
            
         );

         FormClosing += (s,e) =>
          
            _exit = true;
            _thread.Join();
         ;

         _thread.Start();
      
      

【讨论】:

以上是关于有没有办法强制 Win32 计时器在不空闲时执行?的主要内容,如果未能解决你的问题,请参考以下文章

RTOS训练营上节回顾空闲任务定时器任务执行顺序调度策略和晚课提问

RTOS训练营上节回顾空闲任务定时器任务执行顺序调度策略和晚课提问

C# call Win32 api时,-1如何转换为DWORD

ios空闲时间或使设备进入睡眠状态

如何在win32里面设置一个计时器?

移动端点击返回时强制页面刷新解决办法(pageshow)