2010-02-28 12 views
6

Ich versuche, meine C# -Anwendung Multi-Threading zu machen, weil ich manchmal eine Ausnahme bekomme, die besagt, dass ich einen Thread auf eine unsichere Art und Weise aufgerufen habe. Ich habe noch nie ein Multi-Threading in einem Programm gemacht, also ertragen Sie mit mir, wenn ich mich etwas unwissend in der Sache anhört.Multi-Threading-Aufrufe in Windows Forms-Anwendung?

Die Übersicht meines Programms ist, dass ich eine Performance-Monitoring-Anwendung machen möchte. Dies beinhaltet die Verwendung der Prozess- und Leistungsindikatorklasse in C# zum Starten und Überwachen der Prozessorzeit einer Anwendung und zum Zurücksenden dieser Nummer an die Benutzeroberfläche. In der Methode, die tatsächlich die nextValue-Methode des Leistungszählers aufruft (die aufgrund eines Timers jede Sekunde ausgeführt wird), erhalte ich manchmal die oben erwähnte Ausnahme, die darüber spricht, einen Thread unsicher aufzurufen.

Ich habe einen Teil des Codes für Ihre Kenntnisnahme beigefügt. Ich weiß, dass das eine zeitraubende Frage ist, also wäre ich wirklich dankbar, wenn mir jemand helfen könnte, wo ich einen neuen Thread erstellen könnte und wie man ihn sicher nennt. Ich habe versucht zu sehen, was auf MSDN war, aber das hat mich irgendwie verwirrt.

private void runBtn_Click(object sender, EventArgs e) 
{ 
    // this is called when the user tells the program to launch the desired program and 
    // monitor it's CPU usage. 

    // sets up the process and performance counter 
    m.runAndMonitorApplication(); 

    // Create a new timer that runs every second, and gets CPU readings. 
    crntTimer = new System.Timers.Timer(); 
    crntTimer.Interval = 1000; 
    crntTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent); 
    crntTimer.Enabled = true; 
} 

private void OnTimedEvent(object source, ElapsedEventArgs e) 
{ 
    // get the current processor time reading 
    float cpuReading = m.getCPUValue(); 

    // update the current cpu label 
    crntreadingslbl.Text = cpuReading.ToString(); // 

} 
// runs the application 
public void runAndMonitorApplication() 
{ 
    p = new Process(); 
    p.StartInfo.UseShellExecute = true; 
    p.StartInfo.CreateNoWindow = true; 
    p.StartInfo.FileName = fileName; 
    p.Start(); 

    pc = new System.Diagnostics.PerformanceCounter("Process", 
       "% Processor Time", 
       p.ProcessName, 
       true); 
} 

// This returns the current percentage of CPU utilization for the process 
public float getCPUValue() 
{ 
    float usage = pc.NextValue(); 

    return usage; 
} 

Antwort

7

Check out Jon Skeet Artikel über Multi-Threading, insbesondere die Seite auf multi-threading winforms. Es sollte dich richtig fixieren.

Grundsätzlich müssen Sie überprüfen, ob ein Aufruf erforderlich ist, und führen Sie den Aufruf dann bei Bedarf aus. Nach dem Lesen des Artikels sollten Sie in der Lage sein, Ihre UI-Update-Code in Blöcke, die wie folgt aussehen Refactoring:

private void OnTimedEvent(object source, ElapsedEventArgs e) 
{ 
    // get the current processor time reading 
    float cpuReading = m.getCPUValue(); 

    if (InvokeRequired) 
    { 
     // We're not in the UI thread, so we need to call BeginInvoke 
     BeginInvoke(new Action(() => crntreadingslbl.Text = cpuReading.ToString())); 
     return; 
    } 
    // Must be on the UI thread if we've got this far 
    crntreadingslbl.Text = cpuReading.ToString(); 
} 

In Ihrem Code wird ein invoke erforderlich sein, weil Sie einen Timer verwenden. Gemäß der Dokumentation für System.Timers.Timer:

Das Elapsed-Ereignis wird in einem ThreadPool-Thread ausgelöst.

Das bedeutet, dass der OnTimedEvent() -Methode, die Sie als die Delegierten Timer eingestellt auf dem nächsten verfügbaren Thread Thread ausgeführt werden, die definitiv nicht Ihr UI-Thread sein werden. Die Dokumentation schlägt auch eine alternative Möglichkeit, dieses Problem zu lösen:

Wenn Sie den Timer mit einem Benutzer Interface-Elemente, wie eine Form oder Steuerung verwenden, weist das Formular oder Steuerelement , die den Timer auf das enthält SynchronizingObject Eigenschaft, so dass das Ereignis zum Benutzer Schnittstelle Thread marshaliert wird.

Sie können diese Route einfacher finden, aber ich habe es nicht versucht.

+0

Okay, das der Hintergrund Arbeiter Kommentar ziemlich nützlich erscheinen; Aber wie ich es verstehe, läuft der Prozess selbst im UI-Thread, aber ich muss einen separaten Thread erstellen, um Daten zu diesem Prozess zu sammeln und zu aktualisieren. Wie kann ich im Allgemeinen sagen, wo ein separater Thread erstellt werden soll? – Waffles

+0

Der Zeitgeber wird den angeforderten ElapsedEventHandler-Delegaten für den ersten verfügbaren ThreadPool-Thread ausführen, wenn der Zeitgeber "ausgeht". Was immer Sie gefragt haben, der Timer wird in einem separaten Thread ausgeführt, nicht im UI-Thread. Wenn Sie einen Hintergrund-Worker hinzufügen, wird nur ein weiterer Thread in die Gleichung eingefügt. –

0

Ihr Problem, denke ich, ist, dass diese Linie:

crntreadingslbl.Text = cpuReading.ToString(); 

außerhalb des UI-Thread ausgeführt wird. Sie können ein Oberflächenelement außerhalb des UI-Threads nicht aktualisieren. Sie müssen Invoke im Fenster aufrufen, um eine neue Methode im UI-Thread aufzurufen.

Alles, was gesagt wurde, warum nicht perfmon verwenden? Es ist für den Zweck gebaut.

0

Die BackGroundWorker Komponente kann Ihnen helfen. Es ist in der Toolbox verfügbar, sodass Sie es in Ihr Formular ziehen können.

Diese Komponente enthält eine Gruppe von Ereignissen, um Aufgaben in einem anderen Thread als dem UI-Thread auszuführen. Sie müssen sich keine Gedanken darüber machen, einen Thread zu erstellen.

Die gesamte Interaktion zwischen dem Code, der im Hintergrund ausgeführt wird, und den UI-Steuerelementen muss über die Event-Handler erfolgen.

Für Ihr Szenario können Sie einen Timer einrichten, der den Hintergrundarbeiter in einem bestimmten Intervall auslöst.

private void OnTimedEvent(object source, ElapsedEventArgs e) 
{ 
    backgroundWorker.RunWorkerAsync(); 
} 

Dann implementieren Sie die richtigen Event-Handler, um tatsächlich Daten zu sammeln und aktualisieren Sie die UI

private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e) 
{ 
    // Collect performance data and update the UI 
}