我正在编写一个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,因此可能更容易理解。
答案 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.Mutex在myApplication
和otherApplication
之间进行通信。这样,otherApplication
可以告诉您何时完成初始化。
答案 3 :(得分:1)
这是一个经典的海森堡问题,你会通过测量来影响测试的结果。
托管应用的启动时间通常由冷启动时间决定。文件系统查找所有程序集所需的时间。与热启动时间相反,当程序集存在于文件系统缓存中时,您看到的数字要小得多。然后,您只能看到JIT编译器编译程序启动代码所需的时间。您的测试始终是一个热门的开始,因为运行测试代码会将.NET程序集放入文件系统缓存中。幸运的是,数字,但不准确。
您必须在非托管代码中编写测试。您熟悉的一些脚本语言。通过在主窗体的Shown事件中调用Application.Exit()来帮助它,以便在第一个窗体可见时立即退出程序。您现在只需要测量进程执行的时间。可以像.bat文件一样简单,如下所示:
time /t
start /wait yourapp.exe
time /t
在您运行测试之前重新启动计算机,这样您就可以确定Heisenberg教授不在身边。请记住,您测量的确切数字不会影响您客户的机器。因为它是如此依赖于硬盘的状态。仅用它来衡量代码的增量改进是否成功。