如何获得应用程序启动所需的时间?

时间:2010-09-09 18:19:10

标签: c# time startup

我正在编写一个C#应用程序,它需要能够告诉某个应用程序打开需要多长时间。我使用秒表类作为我的计时器。开始时间很简单,因为我完全使用运行.exe的调用来设置它。问题是找出程序打开时的时间。我唯一能想到的就是使用一个PerformanceCounter对象,并使用while循环检查CPU%是否小于某个数字。

目前我正在使用PerformanceCounter,但我没有幸运显示CPU%(它总是显示0)。我的猜测是,应用程序打开的速度比PerformanceCounter可以检查CPU%的速度快,或者PerformanceCounter没有看到进程名称,因为我调用的速度太快(我非常怀疑后者是因为如果发生这种情况,我想我会收到错误。)

还有其他方法可以解决这个问题吗?有没有什么我可能做错了,一直给我一个0%的CPU?我不是在申请之外寻找外部工具。以下是我的代码示例:

otherApp = new otherApplication.Application();
PerformanceCounter cpuCounter = new PerformanceCounter("Process",
"% Processor Time", "otherApplication");
//This next line is to check if PerformanceCounter object is working
//MessageBox.Show(cpuCounter.NextValue().ToString());
stopwatch.Start();
while (cpuCounter.NextValue() > 10)
{
}
stopwatch.Stop();

已编辑:更改了代码以说明otherApp和otherApplication而不是myApp和myApplication,因此可能更容易理解。

4 个答案:

答案 0 :(得分:3)

如果您的应用程序是一个窗口应用程序,即如果它是一个带有消息循环的进程,则标准方法是使用Process.WaitForInputIdle方法。

此方法将阻止,直到相应的进程第一次达到空闲状态。这是创建应用程序主窗口时的状态,可以将消息发送到应用程序 1

该方法的名称有点令人困惑,应该really be called WaitForProcessStartupComplete

using System;
using System.Diagnostics;

class StartupWatch
{
    static void Main()
    {
        string application = "calc.exe";
        Stopwatch sw = Stopwatch.StartNew();
        Process process = Process.Start(application);
        process.WaitForInputIdle();
        Console.WriteLine("Time to start {0}: {1}", application, sw.Elapsed);
    }
}

1 请注意,在应用程序完全就绪之前,可能会在后台线程中进行进一步的初始化。但是,能够处理窗口消息可能是完全启动的应用程序的最清晰定义。

<强>更新

如果需要测量COM服务器的启动时间,您仍然可以使用Process.Start,然后使用AccessibleWindowFromObject访问实际的COM对象以进行自动化。该过程有点复杂,您需要知道可访问对象的窗口类名称。

下面是如何测量Word的启动时间并同时获取Word.Application对象的示例,请参阅注释,了解如何调整它以适合您的COM服务器。

using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using Word = Microsoft.Office.Interop.Word;

[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("00020400-0000-0000-C000-000000000046")]
public interface IDispatch
{
}

class StartupWatch
{
    [DllImport("user32.dll", SetLastError = true)]
    static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

    [DllImport("Oleacc.dll")]
    static extern int AccessibleObjectFromWindow(IntPtr hwnd, uint dwObjectID, byte[] riid, out IDispatch ptr);

    public delegate bool EnumChildCallback(IntPtr hwnd, ref IntPtr lParam);

    [DllImport("User32.dll")]
    public static extern bool EnumChildWindows(IntPtr hWndParent, EnumChildCallback lpEnumFunc, ref IntPtr lParam);

    [DllImport("User32.dll")]
    public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);

    public static bool EnumChildProc(IntPtr hwndChild, ref IntPtr lParam)
    {
        StringBuilder buf = new StringBuilder(128);
        GetClassName(hwndChild, buf, 128);
        if (buf.ToString() == "_WwG")
        {
            lParam = hwndChild;
            return false;
        }
        return true;
    }

    static Word.Application GetWordApplicationObject(Process process)
    {
        Word.Application wordApp = null;
        if (process.MainWindowHandle != IntPtr.Zero)
        {
            IntPtr hwndChild = IntPtr.Zero;

            // Search the accessible child window (it has class name "_WwG") 
            // as described in http://msdn.microsoft.com/en-us/library/dd317978%28VS.85%29.aspx
            //
            // adjust this class name inside EnumChildProc accordingly if you are 
            // creating another COM server than Word
            //
            EnumChildCallback cb = new EnumChildCallback(EnumChildProc);
            EnumChildWindows(process.MainWindowHandle, cb, ref hwndChild);

            if (hwndChild != IntPtr.Zero)
            {
                // We call AccessibleObjectFromWindow, passing the constant OBJID_NATIVEOM (defined in winuser.h) 
                // and IID_IDispatch - we want an IDispatch pointer into the native object model.
                //
                const uint OBJID_NATIVEOM = 0xFFFFFFF0;
                Guid IID_IDispatch = new Guid("{00020400-0000-0000-C000-000000000046}");
                IDispatch ptr;

                int hr = AccessibleObjectFromWindow(hwndChild, OBJID_NATIVEOM, IID_IDispatch.ToByteArray(), out ptr);
                if (hr >= 0)
                {
                    // possibly adjust the name of the property containing the COM  
                    // object accordingly
                    // 
                    wordApp = (Word.Application)ptr.GetType().InvokeMember("Application", BindingFlags.GetProperty, null, ptr, null);
                }
            }
        }
        return wordApp;
    }

    static void Main(string[] args)
    {
        Stopwatch sw = Stopwatch.StartNew();
        Process process = Process.Start(@"C:\Program Files (x86)\Microsoft Office\Office12\WINWORD.EXE");
        process.WaitForInputIdle();
        Console.WriteLine("Time to start {0}: {1}", "Word", sw.Elapsed);
        Word.Application wordApp = GetWordApplicationObject(process);
        Console.WriteLine(string.Format("Word version is: {0}", wordApp.Version));
    }
}

答案 1 :(得分:2)

您可能最好在代码中使用已知位置(在完成所有初始化工作时调用的特定方法),而不是依赖CPU利用率作为启发式方法。

答案 2 :(得分:2)

如果您拥有应用程序和正在启动的应用程序的代码,则可以使用System.Threading.MutexmyApplicationotherApplication之间进行通信。这样,otherApplication可以告诉您何时完成初始化。

答案 3 :(得分:1)

这是一个经典的海森堡问题,你会通过测量来影响测试的结果。

托管应用的启动时间通常由冷启动时间决定。文件系统查找所有程序集所需的时间。与热启动时间相反,当程序集存在于文件系统缓存中时,您看到的数字要小得多。然后,您只能看到JIT编译器编译程序启动代码所需的时间。您的测试始终是一个热门的开始,因为运行测试代码会将.NET程序集放入文件系统缓存中。幸运的是,数字,但不准确。

您必须在非托管代码中编写测试。您熟悉的一些脚本语言。通过在主窗体的Shown事件中调用Application.Exit()来帮助它,以便在第一个窗体可见时立即退出程序。您现在只需要测量进程执行的时间。可以像.bat文件一样简单,如下所示:

time /t
start /wait yourapp.exe
time /t

在您运行测试之前重新启动计算机,这样您就可以确定Heisenberg教授不在身边。请记住,您测量的确切数字不会影响您客户的机器。因为它是如此依赖于硬盘的状态。仅用它来衡量代码的增量改进是否成功。