5 Stimmen

Wie kann man die Zeit ermitteln, die eine Anwendung zum Starten benötigt?

Ich schreibe eine C#-Anwendung, die in der Lage sein muss, festzustellen, wie viel Zeit es dauert, bis eine bestimmte Anwendung geöffnet wird. Ich verwende die Stopwatch-Klasse als Zeitgeber. Die Startzeit ist einfach, da ich sie genau mit dem Aufruf zum Ausführen der exe-Datei festlege. Das Problem ist, herauszufinden, wie man die Zeit bestimmt, wenn das Programm fertig geöffnet ist. Das Einzige, was mir dazu einfällt, ist ein PerformanceCounter-Objekt zu verwenden und zu prüfen, wann CPU% unter einer bestimmten Zahl liegt, indem ich eine while-Schleife verwende.

Im Moment verwende ich PerformanceCounter, aber ich habe kein Glück damit, dass es die CPU% anzeigt (es zeigt immer 0 an). Meine Vermutung dafür ist entweder, dass die Anwendung schneller öffnet, als der PerformanceCounter für die CPU% überprüfen kann, oder dass der PerformanceCounter den Prozessnamen nicht sieht, weil ich es zu schnell aufrufe (ich bezweifle das letztere aufgrund der Tatsache, dass ich denke, ich würde Fehler erhalten, wenn dies geschieht).

Gibt es noch andere Möglichkeiten, dieses Problem zu lösen? Gibt es etwas, das ich vielleicht falsch mache, so dass ich ständig 0 CPU% habe? Ich bin nicht auf der Suche nach externen Tools außerhalb meiner Anwendung. Hier ist ein Beispiel für meinen Code:

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

Bearbeitet: Der Code wurde geändert, so dass es jetzt otherApp und otherApplication heißt, anstatt myApp und myApplication, damit es leichter zu verstehen ist.

3voto

Dirk Vollmar Punkte 166522

Wenn Ihre Anwendung eine Fensteranwendung ist, d.h. wenn es sich um einen Prozess mit einer Nachrichtenschleife handelt, dann wäre der Standardweg die Verwendung der Process.WaitForInputIdle Methode.

Diese Methode blockiert, bis der betreffende Prozess zum ersten Mal den Ruhezustand erreicht hat. Dies ist der Zustand, in dem das Hauptfenster der Anwendung erstellt wird und es möglich ist, Nachrichten an die Anwendung zu senden 1 .

Der Name der Methode ist ein wenig verwirrend, er sollte wirklich WaitForProcessStartupComplete genannt werden .

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 Beachten Sie, dass möglicherweise weitere Initialisierungen in einem Hintergrund-Thread durchgeführt werden, bis die Anwendung vollständig fertig ist. Die Fähigkeit, Fenstermeldungen zu verarbeiten, ist jedoch wahrscheinlich die klarste Definition dafür, dass eine Anwendung vollständig gestartet ist.

Aktualisierung:

Wenn Sie die Startzeit eines COM-Servers messen müssen, können Sie immer noch die Process.Start und verwenden Sie dann AccessibleWindowFromObject um auf das eigentliche COM-Objekt für die Automatisierung zuzugreifen. Das Verfahren ist etwas kompliziert und Sie müssen den Fensterklassennamen des zugänglichen Objekts kennen.

Im Folgenden finden Sie ein Beispiel dafür, wie Sie die Startzeit von Word messen können und eine Word.Application Objekt gleichzeitig zu verwenden, siehe die Kommentare, wie Sie es an Ihren COM-Server anpassen müssen.

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

2voto

LBushkin Punkte 124894

Es ist möglicherweise besser, eine bekannte Stelle in Ihrem Code zu verwenden (eine bestimmte Methode, die aufgerufen wird, wenn alle Initialisierungsarbeiten abgeschlossen sind), als sich auf die CPU-Auslastung als Heuristik zu verlassen.

2voto

davisoa Punkte 5339

Wenn Sie den Code sowohl für Ihre Anwendung als auch für die Anwendung, die Sie starten, besitzen, können Sie System.Threading.Mutex zur Kommunikation zwischen myApplication y otherApplication . Hier entlang, otherApplication kann Ihnen mitteilen, wann er seine Initialisierung abgeschlossen hat.

1voto

Hans Passant Punkte 894572

Dies ist ein klassisches Heisenberg-Problem: Sie beeinflussen das Ergebnis des Tests durch Ihre Messung.

Die Startzeit einer verwalteten Anwendung wird normalerweise von der Kaltstartzeit dominiert. Die Zeit, die das Dateisystem benötigt, um alle Baugruppen zu finden. Im Gegensatz zur Warmstartzeit, einer viel kleineren Zahl, die Sie sehen, wenn die Assemblies im Dateisystem-Cache vorhanden sind. Sie sehen dann nur die Zeit, die der JIT-Compiler benötigt, um den Startcode Ihres Programms zu kompilieren. Ihr Test wird immer ein Warmstart sein, weil das Ausführen des Testcodes die .NET-Assemblies in den Dateisystem-Cache legt. Das sind zwar schöne Zahlen, aber nicht genau.

Sie müssen Ihren Test in nicht verwaltetem Code schreiben. Eine Skriptsprache, mit der Sie vertraut sind. Helfen Sie ihm dabei, indem Sie Application.Exit() im Shown-Ereignis des Hauptformulars aufrufen, damit Ihr Programm sofort beendet wird, wenn das erste Formular sichtbar ist. Jetzt müssen Sie nur noch messen, wie lange der Prozess ausgeführt wurde. Das kann eine einfache .bat-Datei sein, die wie folgt aussieht:

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

Starten Sie den Rechner neu, bevor Sie den Test durchführen, damit Sie sicher sein können, dass Prof. Heisenberg nicht in der Nähe ist. Und bedenken Sie, dass die genaue Zahl, die Sie messen, auf dem Rechner Ihres Kunden wahrscheinlich nicht reproduzierbar ist. Denn sie hängt stark vom Zustand der Festplatte ab. Verwenden Sie sie nur, um den Erfolg inkrementeller Verbesserungen an Ihrem Code zu messen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X