2010-09-09 3 views
5

Sto scrivendo un'applicazione C# che deve essere in grado di dire quanto tempo ci vuole per aprire una determinata applicazione. Sto usando il cronometro come timer. L'ora di inizio è semplice poiché l'ho impostata esattamente con la chiamata per eseguire l'exe. Il problema è trovare il momento in cui il programma ha terminato l'apertura. L'unica cosa che potrei pensare di testare questo è usare un oggetto PerformanceCounter e controllare quando la% CPU è inferiore a un certo numero usando un ciclo while.Come ottenere il tempo necessario all'avvio di un'applicazione?

Al momento sto utilizzando PerformanceCounter, ma non ho fortuna con la visualizzazione della CPU% (è sempre visualizzato 0). La mia ipotesi è che l'applicazione si apra più velocemente di quanto PerformanceCounter possa controllare la CPU% o che il PerformanceCounter non veda il nome del processo perché lo chiamo troppo velocemente (dubito fortemente di quest'ultima perché il fatto che Penso che otterrei errori se ciò accadesse).

Esistono altri modi per risolvere questo problema? C'è qualcosa che potrei fare male che mi sta dando sempre una CPU pari a 0? Non sto cercando strumenti esterni al di fuori della mia applicazione. Ecco un esempio del mio codice:

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

Modificato: codice modificato dire Otherapp e otherApplication invece di myApp e myApplication, in modo che possa essere più facile da capire.

risposta

2

Potrebbe essere meglio utilizzare una posizione nota nel codice (un metodo particolare che viene chiamato quando viene completato tutto il lavoro di inizializzazione) piuttosto che basarsi sull'utilizzo della CPU come euristica.

+0

Sto esaminando anche questo. Poiché sto utilizzando una chiamata per creare un'istanza di un'applicazione tramite ActiveX, potrei essere in grado di utilizzare un semplice comando che può essere elaborato dall'applicazione solo dopo averlo caricato. Ciò dovrebbe darmi il più vicino possibile a un "tempo di avvio dell'applicazione". – Brundle

+0

Ho finito per utilizzare una proprietà dall'altra applicazione dopo la "nuova" chiamata. Poi ho fermato il timer. In quel modo sapevo che la creazione era completamente finita. Sì, potrebbe esserci un po 'di tempo in più a causa della chiamata di proprietà, ma questo non influirà sul mio obiettivo finale. – Brundle

2

Se si possiede il codice sia per l'applicazione che per l'applicazione che si sta iniziando, è possibile utilizzare System.Threading.Mutex per comunicare tra myApplication e otherApplication. In questo modo, otherApplication può dirti quando ha terminato l'inizializzazione.

3

Se l'applicazione è un'applicazione finestra, vale a dire se si tratta di un processo con un loop di messaggi, il modo standard sarebbe utilizzare il metodo Process.WaitForInputIdle.

Questo metodo bloccherà fino a quando il rispettivo processo ha raggiunto lo stato di inattività per la prima volta. Questo è lo stato in cui viene creata la finestra principale dell'applicazione e è possibile inviare messaggi all'applicazione .

Il nome del metodo è un po 'confuso, dovrebbe essere 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); 
    } 
} 

noti che ci potrebbe essere ulteriormente inizializzazione in corso in un thread in background finché l'applicazione è completamente pronto. Tuttavia, essere in grado di gestire i messaggi della finestra è probabilmente la definizione più chiara di un'applicazione avviata completamente.

Aggiornamento:

Se è necessario misurare il tempo di avvio di un server COM è comunque possibile utilizzare Process.Start e quindi utilizzare AccessibleWindowFromObject per accedere all'oggetto COM effettivo per l'automazione. La procedura è un po 'complicata e dovrai conoscere il nome della classe della finestra dell'oggetto accessibile.

Di seguito è un esempio di come è possibile misurare il tempo di avvio di Word e ottenere un oggetto Word.Application allo stesso tempo, vedere i commenti come si dovrebbe regolare per soddisfare il server 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

Sto esaminando questo al momento. In realtà sto usando un oggetto ActiveX per creare l'istanza dell'applicazione, quindi non penso che Process.Start possa funzionare per me. Ma se c'è un modo per usare la classe Process con un nome di processo da Task Manager di Windows per farlo, sarebbe interessante. – Brundle

+1

@Brundle: utilizzando 'Process.GetProcessById (int processId)' o 'Process.GetProcessesByName (string processName)' è possibile ottenere un processo utilizzando le informazioni visualizzate in Task Manager. Tuttavia, ciò non sarà utile se si desidera misurare le prestazioni di avvio, semplicemente perché l'applicazione deve già essere avviata per essere visualizzata in Task Manager. –

+0

Ho appena provato Process.GetProcessesByName (myApplication) con il processo [0] .WaitForInputIdle() e sembra funzionare. Anche se ho avuto tempi simili quando ho appena preso quelle due righe di codice, quindi è molto difficile da dire. – Brundle

1

Questo è un classico problema di Heisenberg, influisce sull'esito del test tramite la misurazione.

Il tempo di avvio di un'app gestita è normalmente dominato dall'ora di avvio a freddo. Il tempo richiesto dal file system per trovare tutti gli assembly. A differenza del tempo di avvio a caldo, un numero molto più piccolo si vede quando gli assembly sono presenti nella cache del file system. Vedrai quindi solo la quantità di tempo necessaria al compilatore JIT per compilare il codice di avvio del tuo programma. Il test sarà sempre un avvio a caldo perché l'esecuzione del codice di test inserisce gli assembly .NET nella cache del file system. Numeri felici, certo, ma non accurati.

Dovrai scrivere il test in codice non gestito. Qualche linguaggio di scripting con cui hai familiarità. Aiutatelo chiamando Application.Exit() nell'evento Mostrato del modulo principale in modo che il vostro programma si chiuda immediatamente quando il primo modulo è visibile. Ora devi solo misurare per quanto tempo il processo è stato eseguito. Potrebbe essere semplice come file .bat che assomiglia a questo:

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

Riavviare il computer prima di eseguire il test in modo da poter essere sicuri che il Prof. Heisenberg non è in giro. E tieni presente che il numero esatto che misura non verrà probabilmente riprodotto sulla macchina del cliente. Perché è così dipendente dallo stato del disco rigido. Usalo solo per misurare il successo dei miglioramenti incrementali del tuo codice.

+0

Ottimi punti, ma dall'esempio dell'OP potrebbe anche accadere che 'otherApplication.Application()' si riferisca a un server COM esterno come Word o Excel. –

+0

@ 0xA3 - vero, non l'avevo considerato. Non ha molto senso misurare che, però, non può rendere i programmi di Office più veloci. –

Problemi correlati