Windows计时器队列计时器在.NET断点之后继续

时间:2023-01-18 09:40:38

I developed a .NET application that has to poll a pressure device with 120 Hz. The standard .NET timers only seem to achieve up to 60 Hz, so I decided to use the Win32 CreateTimerQueueTimer API via PInvoke. It works nicely, but the debugging experience is very bad because the timers are even fired when I'm stepping through the program while the program is on hold. I wrote a minimal example in C and in C# and the undesired behavior only occurs on C#. The C program does not create the timer callback threads while the debugger paused the program. Can anyone tell me, what I can do to achieve the same debugging behavior in C#?

我开发了一个.NET应用程序,必须以120 Hz的频率轮询压力设备。标准的.NET计时器似乎只能达到60 Hz,因此我决定通过PInvoke使用Win32 CreateTimerQueueTimer API。它工作得很好,但是调试体验非常糟糕,因为当程序处于暂停状态时,我正在逐步执行程序时,计时器甚至会被触发。我在C和C#中编写了一个最小的例子,不希望的行为只发生在C#上。当调试器暂停程序时,C程序不会创建计时器回调线程。任何人都可以告诉我,我可以做些什么来实现C#中相同的调试行为?

C code:

C代码:

#include <stdio.h>
#include <assert.h>
#include <Windows.h>

int counter = 0;

VOID NTAPI callback(PVOID lpParameter, BOOLEAN TimerOrWaitFired)
{
    printf("Just in time %i\n", counter++);
}

int main()
{
    HANDLE timer;
    BOOL success = CreateTimerQueueTimer(&timer, NULL, callback, NULL, 0, 1000, WT_EXECUTEDEFAULT);
    assert(FALSE != success); // set breakpoint at this line and wait 10 seconds
    Sleep(1000);
    success = DeleteTimerQueueTimer(NULL, timer, NULL); // step to this line
    assert(FALSE != success);
    return 0;
}

C result

C结果

Equivalent C# code:

等价的C#代码:

using System;
using System.Runtime.InteropServices;

class TimerQueue
{
    delegate void WAITORTIMERCALLBACK(IntPtr lpParameter, bool TimerOrWaitFired);

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool CreateTimerQueueTimer(
        out IntPtr phNewTimer,
        IntPtr TimerQueue,
        WAITORTIMERCALLBACK Callback,
        IntPtr Parameter,
        uint DueTime,
        uint Period,
        uint Flags);

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool DeleteTimerQueueTimer(
        IntPtr TimerQueue,
        IntPtr Timer,
        IntPtr CompletionEvent);

    static int counter = 0;

    static void Callback(IntPtr lpParameter, bool TimerOrWaitFired)
    {
        Console.WriteLine("Just in time {0}", counter++);
    }

    static void Main(string[] args)
    {
        WAITORTIMERCALLBACK callbackWrapper = Callback;
        IntPtr timer;
        bool success = CreateTimerQueueTimer(out timer, IntPtr.Zero, callbackWrapper, IntPtr.Zero, 0, 1000, 0);
        System.Diagnostics.Debug.Assert(false != success); // set breakpoint at this line and wait 10 seconds
        System.Threading.Thread.Sleep(1000);
        success = DeleteTimerQueueTimer(IntPtr.Zero, timer, IntPtr.Zero); // step to this line
        System.Diagnostics.Debug.Assert(false != success);
    }
}

C# result

C#结果

By the way, I know there is a race condition when using the unprotected counter variable from multiple threads, that's not important right now.

顺便说一句,我知道当使用来自多个线程的不受保护的计数器变量时存在竞争条件,这现在并不重要。

The sleep for one second is meant independent from waiting after the breakpoint is hit and seems to be necessary because the callbacks are not queued immediately on the process even when stepping the program in a debugger but only after a short delay.

休眠一秒钟意味着在断点被击中后独立于等待并且似乎是必要的,因为即使在调试器中步进程序但仅在短暂延迟之后,回调也不会立即在进程上排队。

The call to DeleteTimerQueueTimer is not really necessary to show my problem because it occurs before this line is executed.

对DeleteTimerQueueTimer的调用对于显示我的问题并不是必需的,因为它在执行此行之前发生。

2 个解决方案

#1


0  

You should be able to go into the Debug->Windows->Threads windows and Freeze all threads when you are stepping.

您应该可以进入Debug-> Windows-> Threads窗口并在步进时冻结所有线程。

#2


-1  

As the C can access functions of system (kernel32) directly, in compare to C# it is more direct and less safe access. C# is compiled at runtime and all calls to outside world (DLLs / COM), it has to use Wrapper provided by OS (Windows).

由于C可以直接访问系统(kernel32)的功能,与C#相比,它更直接,安全性更低。 C#是在运行时编译的,所有调用都是外部世界(DLLs / COM),它必须使用OS提供的Wrapper(Windows)。

As PhillipH wrote: Check the Threads window during breakpoint. In C# code, You will see there are Threads like RunParkingWindow and .NET SystemEvent.

正如PhillipH所写:在断点期间检查线程窗口。在C#代码中,您将看到有像RunParkingWindow和.NET SystemEvent这样的线程。

Comparing with C program, there are is only 1 thread - the one You have just stopped for debug.

与C程序相比,只有一个线程 - 您刚刚停止调试的线程。

Realizing this You will understand that, the breakpoint in debug will just stop the thread You are currently on, but the other threads will run further. Which also means the Wrappers provided by OS are still listening to any events and just setting them in queue. Once Your app continues to run, they will throw all they can towards it and Callback method will do the rest.

实现这一点您将理解,调试中的断点将停止您当前所在的线程,但其他线程将继续运行。这也意味着操作系统提供的Wrappers仍在监听任何事件,只是将它们设置在队列中。一旦你的应用程序继续运行,他们就会向它抛出所有可能的东西,Callback方法将完成其余的工作。

I tried to find any relevant article / picture with data flow, but I wasn't successful. Please consider the text above just as an opinion more than a technical paragraph.

我试图找到任何与数据流相关的文章/图片,但我没有成功。请将上述案文视为一个意见而非技术段落。

#1


0  

You should be able to go into the Debug->Windows->Threads windows and Freeze all threads when you are stepping.

您应该可以进入Debug-> Windows-> Threads窗口并在步进时冻结所有线程。

#2


-1  

As the C can access functions of system (kernel32) directly, in compare to C# it is more direct and less safe access. C# is compiled at runtime and all calls to outside world (DLLs / COM), it has to use Wrapper provided by OS (Windows).

由于C可以直接访问系统(kernel32)的功能,与C#相比,它更直接,安全性更低。 C#是在运行时编译的,所有调用都是外部世界(DLLs / COM),它必须使用OS提供的Wrapper(Windows)。

As PhillipH wrote: Check the Threads window during breakpoint. In C# code, You will see there are Threads like RunParkingWindow and .NET SystemEvent.

正如PhillipH所写:在断点期间检查线程窗口。在C#代码中,您将看到有像RunParkingWindow和.NET SystemEvent这样的线程。

Comparing with C program, there are is only 1 thread - the one You have just stopped for debug.

与C程序相比,只有一个线程 - 您刚刚停止调试的线程。

Realizing this You will understand that, the breakpoint in debug will just stop the thread You are currently on, but the other threads will run further. Which also means the Wrappers provided by OS are still listening to any events and just setting them in queue. Once Your app continues to run, they will throw all they can towards it and Callback method will do the rest.

实现这一点您将理解,调试中的断点将停止您当前所在的线程,但其他线程将继续运行。这也意味着操作系统提供的Wrappers仍在监听任何事件,只是将它们设置在队列中。一旦你的应用程序继续运行,他们就会向它抛出所有可能的东西,Callback方法将完成其余的工作。

I tried to find any relevant article / picture with data flow, but I wasn't successful. Please consider the text above just as an opinion more than a technical paragraph.

我试图找到任何与数据流相关的文章/图片,但我没有成功。请将上述案文视为一个意见而非技术段落。