2010-09-09 7 views
5

Estoy escribiendo una aplicación C# que necesita poder decir cuánto tiempo demora abrir una determinada aplicación. Estoy usando la clase Cronómetro como mi temporizador. La hora de inicio es fácil ya que la configuré exactamente con la llamada para ejecutar el .exe. El problema es descubrir cómo programar el momento en que el programa finaliza la apertura. Lo único que se me ocurre para probar esto es usar un objeto PerformanceCounter y verificar cuando el% de CPU es menor que cierto número usando un ciclo while.¿Cómo obtener el tiempo necesario para que una aplicación se inicie?

Por el momento estoy usando PerformanceCounter, pero no estoy teniendo suerte mostrando la CPU% (siempre muestra 0). Mi suposición es que la aplicación se abre más rápido de lo que PerformanceCounter puede verificar el% de la CPU o que el PerformanceCounter no está viendo el nombre del proceso porque lo estoy llamando demasiado rápido (lo dudo mucho por el hecho de que Creo que obtendría errores si esto sucediera).

¿Hay alguna otra forma de resolver este problema? ¿Hay algo que pueda estar haciendo mal que me dé un 0% de CPU todo el tiempo? No estoy buscando herramientas externas fuera de mi aplicación. Aquí está una muestra de mi código:

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(); 

Editado: código cambiado decir OtrosProgramas y otherApplication en lugar de myApp y mi_aplicación, por lo que puede ser más fácil de entender.

Respuesta

2

Puede ser mejor que utilice una ubicación conocida en su código (un método particular que se llama cuando se completa todo el trabajo de inicialización) en lugar de depender de la utilización de la CPU como una heurística.

+0

Estoy investigando esto también. Como estoy usando una llamada para crear una instancia de una aplicación a través de ActiveX, es posible que pueda usar un comando simple que solo puede ser procesado por la aplicación una vez que se haya cargado. Eso me debería dar lo más cercano a un "tiempo de inicio de aplicación" que pueda obtener. – Brundle

+0

Terminé usando una propiedad de la otra aplicación después de la "nueva" llamada. Luego paré el temporizador. De esa manera sabía que la creación estaba completamente terminada. Sí, puede haber un poco de tiempo extra debido a la llamada a la propiedad, pero esto no afectará mi objetivo final. – Brundle

2

Si posee el código para su aplicación y la que está comenzando, puede usar System.Threading.Mutex para comunicarse entre myApplication y otherApplication. De esta forma, otherApplication puede decirle cuándo ha terminado de inicializarse.

3

Si su aplicación es una aplicación de ventana, es decir, si se trata de un proceso con un bucle de mensaje, la forma estándar sería utilizar el método Process.WaitForInputIdle.

Este método se bloqueará hasta que el proceso respectivo haya alcanzado el estado inactivo por primera vez. Este es el estado cuando se crea la ventana principal de la aplicación y está bien enviar mensajes a la aplicación .

El nombre del método es un poco confuso, debería 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); 
    } 
} 

Tenga en cuenta que puede haber más de inicialización pasando en un subproceso en segundo plano hasta que la aplicación está completamente listo. Sin embargo, ser capaz de manejar mensajes de ventana es probablemente la definición más clara de una aplicación que se inició completamente.

Actualización:

Si es necesario medir el tiempo de puesta en marcha de un servidor COM puede seguir utilizando Process.Start y luego usar AccessibleWindowFromObject para acceder al objeto COM real para la automatización. El procedimiento es un poco complicado y deberá conocer el nombre de la clase de ventana del objeto accesible.

A continuación se muestra un ejemplo de cómo se puede medir el tiempo de inicio de Word y obtener un objeto Word.Application al mismo tiempo; consulte los comentarios sobre cómo debe ajustarlo para adaptarlo a su servidor 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

Estoy investigando esto por el momento. De hecho, estoy usando un objeto ActiveX para crear la instancia de la aplicación, por lo que no creo que Process.Start funcione para mí. Pero si hay alguna forma de utilizar la clase Process con un nombre de proceso del Administrador de tareas de Windows para hacer esto, eso sería interesante. – Brundle

+1

@Brundle: Usando 'Process.GetProcessById (int processId)' o 'Process.GetProcessesByName (string processName)' puede hacerse con un proceso usando la información que ve en el Administrador de tareas. Sin embargo, esto no servirá de nada si desea medir el rendimiento de inicio, simplemente porque la aplicación ya debe comenzar a aparecer en el Administrador de tareas. –

+0

Acabo de probar Process.GetProcessesByName (myApplication) con el proceso [0] .WaitForInputIdle() y parece estar funcionando. Aunque tuve momentos similares cuando saqué esas dos líneas de código, así que es muy difícil de decir. – Brundle

1

Este es un problema clásico de Heisenberg, usted afecta el resultado de la prueba por su medida.

El tiempo de inicio de una aplicación administrada normalmente está dominado por el tiempo de arranque en frío. El tiempo que necesita el sistema de archivos para encontrar todos los ensamblajes. A diferencia del tiempo de inicio en caliente, un número mucho más pequeño que ve cuando los ensamblados están presentes en la memoria caché del sistema de archivos. Entonces solo verá la cantidad de tiempo que el compilador JIT necesita para compilar el código de inicio de su programa. Su prueba siempre será un comienzo cálido porque ejecutar el código de prueba coloca los ensamblados de .NET en la memoria caché del sistema de archivos. Números felices, por supuesto, pero no precisos.

Deberá escribir su prueba en código no administrado. Algún lenguaje de scripting con el que esté familiarizado. Ayúdenlo llamando a Application.Exit() en el evento Mostrado del formulario principal para que su programa se cierre inmediatamente cuando el primer formulario esté visible. Ahora solo tiene que medir cuánto tiempo se estaba ejecutando el proceso. Podría ser tan simple como archivo .bat que tiene este aspecto:

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

Reiniciar la máquina antes de ejecutar la prueba de lo que puede estar seguro de que el profesor Heisenberg no es todo. Y tenga en cuenta que el número exacto que mida probablemente no se reproduzca en la máquina de su cliente. Porque es tan dependiente del estado del disco duro. Úselo únicamente para medir el éxito de las mejoras incrementales de su código.

+0

buenos puntos, pero a partir de la muestra de la OP que podrían también ser el caso que 'otherApplication.Application()' hace referencia a un servidor COM externo, como Word o Excel. –

+0

@ 0xA3 - cierto, no lo había considerado. No tiene mucho sentido medir eso, sin embargo, no puede hacer que los programas de Office sean más rápidos. –

Cuestiones relacionadas