【问题标题】:Why does GetThreadTimes return为什么 GetThreadTimes 返回
【发布时间】:2014-10-20 18:57:20
【问题描述】:

我正在尝试测量线程中花费的时间以用于进度报告,但我从 GetThreadTimes 系统调用中得到了非常奇怪的结果。给定以下程序(在 VS 2013 中编译,针对 .NET 4.5):

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;

namespace ThreadTimingTest
{
    class Program
    {
        static Stopwatch _wallClockTimer;

        static System.Timers.Timer _timer = new System.Timers.Timer();

        private static Thread _thread;
        private static IntPtr _threadHandle;

        static void Main(string[] args)
        {
            _timer = new System.Timers.Timer();
            _timer.Elapsed += (s, e) =>
            {
                System.Runtime.InteropServices.ComTypes.FILETIME start, end, rawKernelTime, rawUserTime;

                GetThreadTimes(_threadHandle, out start, out end, out rawKernelTime, out rawUserTime);

                //ref: http://stackoverflow.com/a/6083846
                ulong uLow = (ulong)rawKernelTime.dwLowDateTime;
                ulong uHigh = (uint)rawKernelTime.dwHighDateTime;
                uHigh = uHigh << 32;
                long kernelTime = (long)(uHigh | uLow);

                uLow = (ulong)rawUserTime.dwLowDateTime;
                uHigh = (uint)rawUserTime.dwHighDateTime;
                uHigh = uHigh << 32;
                long userTime = (long)(uHigh | uLow);

                Debug.WriteLine("Kernel time: " + kernelTime);
                Debug.WriteLine("User time: " + userTime);
                Debug.WriteLine("Combined raw execution time: " + (kernelTime + userTime));

                long elapsedMilliseconds = (kernelTime + userTime) / 10000; //convert to milliseconds: raw timing unit is 100 nanoseconds
                Debug.WriteLine("Elapsed thread time: " + elapsedMilliseconds + " milliseconds");

                Debug.WriteLine("Wall Clock Time: " + _wallClockTimer.ElapsedMilliseconds + " milliseconds");

            };

            _timer.Interval = 1000;

            _wallClockTimer = new Stopwatch();

            Debug.WriteLine("Starting...");
            RunTest();

            Debug.WriteLine("Ended.");
        }

        public static void RunTest()
        {
            _thread =
                new Thread
                (
                    () =>
                    {
                        _threadHandle = GetCurrentThread();
                        Stopwatch sw = Stopwatch.StartNew();

                        while (sw.ElapsedMilliseconds < 3000)
                        {
                            int i = 1 + 2;
                        } //do busy-work for 3 seconds

                        sw.Stop();
                    }
                );

            _timer.Start();

            _thread.Start();a

            _wallClockTimer.Start();

            _thread.Join();
        }

        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool GetThreadTimes(IntPtr hThread,
            out System.Runtime.InteropServices.ComTypes.FILETIME lpCreationTime, out System.Runtime.InteropServices.ComTypes.FILETIME lpExitTime,
            out System.Runtime.InteropServices.ComTypes.FILETIME lpKernelTime, out System.Runtime.InteropServices.ComTypes.FILETIME lpUserTime);

        [DllImport("kernel32.dll")]
        private static extern IntPtr GetCurrentThread();
    }
}

我得到以下输出:

Starting...
Kernel time: 0
User time: 0
Combined raw execution time: 0
Elapsed thread time: 0 milliseconds
Wall Clock Time: 1036 milliseconds
Kernel time: 0
User time: 0
Combined raw execution time: 0
Elapsed thread time: 0 milliseconds
Wall Clock Time: 2036 milliseconds
The thread '<No Name>' (0x191c) has exited with code 0 (0x0).
Ended.

我希望GetThreadTimes 报告线程时间不为零的值:为什么报告为零?

【问题讨论】:

  • Multiple managed threads can run on the same OS thread。假设:.NET 运行时在完成其时间片之前中断了操作系统线程,因此不会记录其时间(参见this 文章)。当然,无论如何时间都不准确,因为您不是在测量在 托管 线程中花费的时间,而是在测量底层操作系统线程。
  • 因为 GetCurrentThread() 不会返回您认为的结果。让您测量错误线程的时间。刚刚在this blog post 报道。

标签: c# multithreading concurrency timing


【解决方案1】:

根据 Hans 提供的链接对您的代码进行几个简单的修改后,将显示有效时间。

添加一些互操作声明:

[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool DuplicateHandle(IntPtr hSourceProcessHandle,
    IntPtr hSourceHandle, IntPtr hTargetProcessHandle, out IntPtr lpTargetHandle,
    uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwOptions);

[Flags]
public enum DuplicateOptions : uint
{
    DUPLICATE_CLOSE_SOURCE = (0x00000001), // Closes the source handle. This occurs regardless of any error status returned.
    DUPLICATE_SAME_ACCESS = (0x00000002),  // Ignores the dwDesiredAccess parameter. The duplicate handle has the same access as the source handle.
}

[DllImport("kernel32.dll")]
static extern IntPtr GetCurrentProcess();

然后修改句柄的分配方式:

//_threadHandle = GetCurrentThread();       <-- previous assignment
IntPtr processHandle = GetCurrentProcess();
bool result = DuplicateHandle(processHandle, GetCurrentThread(), processHandle, out _threadHandle, 0, false, (uint) DuplicateOptions.DUPLICATE_SAME_ACCESS);

产生以下结果:

Starting...
Kernel time: 0
User time: 10000000
Combined raw execution time: 10000000
Elapsed thread time: 1000 milliseconds
Wall Clock Time: 1006 milliseconds
Kernel time: 0
User time: 20000000
Combined raw execution time: 20000000
Elapsed thread time: 2000 milliseconds
Wall Clock Time: 2004 milliseconds
Kernel time: 0
User time: 30000000
Combined raw execution time: 30000000
Ended.
Elapsed thread time: 3000 milliseconds
Wall Clock Time: 3045 milliseconds

编辑:

最近在处理为给定系统创建的过多线程方面付出了很大努力。假设您有一个四核处理器,并且要运行 20 多个线程。线程在启动、内核管理、内存(它们有自己的堆栈)等方面具有相当大的成本。与减少线程数相比,系统实际上可能会更慢(处理上下文和调度)。所以在 .NET 中,创建了像 TPL 这样的库(允许开发人员管理任务,而不是线程)。这允许 CLR 平衡目标系统的适当线程数。但是在您的情况下(您显式创建托管线程),与本机线程几乎总是存在一对一的关系。

希望这会有所帮助。

【讨论】:

  • 感谢您的回答!不过,我不清楚一件事:这个解决方案如何解决@Blorgbeard 的观察,即多个托管线程可以在同一个操作系统线程上运行?
  • @Caleb - 我将编辑我的答案(因为解释有点长)。
猜你喜欢
  • 1970-01-01
  • 2021-03-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多