2010-09-09 217 views
5

我正在编写一个C#应用程序,需要能够告诉打开某个应用程序需要多长时间。我使用Stopwatch类作为我的计时器。开始时间很简单,因为我完全用调用来运行.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的,因此,它可能是更容易理解。

回答

2

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

+0

我也在看这个。由于我正在使用调用来通过ActiveX创建应用程序的实例,因此我可能会使用一个简单的命令,只有在应用程序加载后才能处理该命令。这应该让我尽可能接近“应用程序启动时间”。 – Brundle 2010-09-09 20:01:54

+0

在“新建”调用之后,我结束了使用另一个应用程序的属性。然后我停下了计时器。这样我就知道创作完成了。是的,由于房产通话可能会稍微增加一些时间,但这不会影响我的最终目标。 – Brundle 2010-09-09 22:26:02

2

如果您拥有您的应用程序和应用程序的代码,您可以使用System.Threading.MutexmyApplicationotherApplication之间进行通信。这样,otherApplication可以告诉你它什么时候完成初始化。

3

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

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

该方法的名称有点混乱,它应该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); 
    } 
} 

注意,可能会有进一步的初始化在后台线程怎么回事,直到应用程序是完全准备好了。但是,能够处理窗口消息可能是完全启动的应用程序的最清晰定义。

更新:

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

下面是一个示例,您可以如何测量Word的启动时间并同时获得一个对象,请参阅评论如何调整它以适合您的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)); 
    } 
} 
+0

我正在调查此刻。我实际上使用ActiveX对象来创建应用程序的实例,所以我不认为Process.Start会为我工作。但是如果在Windows任务管理器中使用带有进程名称的Process类来执行此操作,那将会很有趣。 – Brundle 2010-09-09 19:59:39

+1

@Brundle:使用'Process.GetProcessById(int processId)'或'Process.GetProcessesByName(string processName)',您可以使用您在任务管理器中看到的信息获取进程。但是,如果您想测量启动性能,这将毫无用处,因为应用程序已经需要启动以显示在任务管理器中。 – 2010-09-09 20:27:34

+0

我刚刚尝试Process.GetProcessesByName(myApplication)与进程[0] .WaitForInputIdle(),它似乎工作。尽管当我把这两行代码拿出来的时候,我有相似的时间,所以很难说。 – Brundle 2010-09-09 20:54:16

1

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

托管应用程序的启动时间通常由冷启动时间控制。文件系统找到所有程序集所需的时间。与热启动时间相反,当组件存在于文件系统缓存中时,会看到更小的数字。然后您只能看到JIT编译器编译程序启动代码所需的时间。由于运行测试代码会将.NET程序集放入文件系统缓存中,因此您的测试始终是热身启动。幸福的数字,可以肯定,但不准确。

您将不得不在非托管代码中编写测试。您熟悉的一些脚本语言。通过在主窗体的Shown事件中调用Application.Exit()来帮助它,以便在第一个窗体可见时立即退出程序。您现在只需要测量过程执行的时间。在运行测试之前

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

重新启动机器,所以你可以肯定的是海森堡教授不在身边:可能是为.bat文件看起来像这样简单。并且请记住,您测量的确切数字不可能在客户机器上重现。因为它依赖于硬盘的状态。仅用它来衡量代码的增量改进是否成功。

+0

好点,但从OP的示例中,'otherApplication.Application()'指的是外部COM服务器(如Word或Excel)也可能是这种情况。 – 2010-09-09 20:24:57

+0

@ 0xA3 - 真的,没有考虑到这一点。虽然没有多少意义,但不能让Office程序更快。 – 2010-09-09 20:44:52

相关问题