2010-02-28 10 views
6

Sto cercando di rendere l'applicazione C# multi-threadata perché a volte, ottengo un'eccezione che dice che ho effettuato una chiamata a un thread in un modo non sicuro. Non ho mai fatto alcuna multi-threading in un programma, quindi abbi pazienza se sembro un po 'ignorante sul problema.Chiamate multi-threading nell'applicazione Windows Forms?

La panoramica del mio programma è che voglio fare un'applicazione di monitoraggio delle prestazioni. Ciò comporta l'utilizzo della classe del contatore di processo e delle prestazioni in C# per avviare e monitorare il tempo di processore di un'applicazione e rinviarlo all'interfaccia utente. Tuttavia, nel metodo che chiama il metodo nextValue del contatore di prestazioni (che è impostato per eseguire ogni secondo grazie a un timer), a volte otterrei l'eccezione di cui sopra che parlerebbe di chiamare un thread in un modo non sicuro.

Ho allegato alcuni dei codici per la vostra lettura. So che questa è una domanda che richiede molto tempo, quindi sarei davvero grato se qualcuno potesse offrirmi un aiuto su dove creare una nuova discussione e su come chiamarla in modo sicuro. Ho provato a vedere cosa c'era su MSDN, ma questo mi ha confuso.

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

risposta

7

Partenza articolo di Jon Skeet su multi-threading, in particolare la pagina su multi-threading winforms. Dovresti sistemarti.

Fondamentalmente è necessario verificare se è richiesto un richiamo e quindi eseguire il richiamo se necessario. Dopo aver letto l'articolo si dovrebbe essere in grado di refactoring il codice UI-aggiornamento in blocchi che assomigliano a questo:

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

Nel codice, un invoke saranno necessari perché si sta utilizzando un timer. In base alla documentazione di System.Timers.Timer:

L'evento trascorso viene generato su una discussione ThreadPool.

Ciò significa che il metodo OnTimedEvent() impostato come delegato del timer verrà eseguito sul thread ThreadPool successivo disponibile, che non sarà sicuramente il thread dell'interfaccia utente. La documentazione suggerisce anche un modo alternativo per risolvere questo problema:

Se si utilizza il Timer con un elemento di interfaccia utente , ad esempio un modulo o controllo, assegnare il modulo o controllo che contiene il Timer al SynchronizingObject proprietà, in modo che l'evento viene eseguito il marshalling per il thread di interfaccia utente .

È possibile trovare questo percorso più semplice, ma non l'ho provato.

+0

Va bene, questo e il commento di sfondo operaio sembra molto utile; ma a quanto ho capito, il processo stesso è in esecuzione sul thread dell'interfaccia utente, ma devo creare una thread separata per raccogliere e aggiornare i dati su quel processo? In generale, come sarò in grado di dire dove creare un thread separato? – Waffles

+0

Il timer eseguirà il delegato ElapsedEventHandler richiesto sul primo thread ThreadPool disponibile quando il timer "si spegne". Quindi qualunque cosa tu abbia chiesto al timer di fare avverrà su un thread separato, non sul thread dell'interfaccia utente. L'aggiunta di un lavoratore in background introdurrà solo un altro thread all'equazione. –

0

Il tuo problema, credo, è che questa linea:

crntreadingslbl.Text = cpuReading.ToString(); 

è in esecuzione di fuori del thread dell'interfaccia utente. Non è possibile aggiornare un elemento dell'interfaccia utente al di fuori del thread dell'interfaccia utente. È necessario chiamare Invoke sulla finestra per chiamare un nuovo metodo sul thread dell'interfaccia utente.

Tutto ciò detto, perché non usare perfmon? È costruito per lo scopo.

0

Il componente BackGroundWorker può essere d'aiuto. È disponibile nella casella degli strumenti in modo da poter trascinare al modulo.

Questo componente espone un insieme di eventi per eseguire attività in un thread diverso dal thread dell'interfaccia utente. Non devi preoccuparti di creare un thread.

Tutte le interazioni tra il codice in esecuzione sullo sfondo ei controlli dell'interfaccia utente devono essere eseguite tramite i gestori di eventi.

Per lo scenario è possibile impostare un timer per attivare l'operatore in background a un intervallo specifico.

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

Poi si implementano i gestori di eventi appropriati per raccogliere in realtà i dati e aggiornare l'interfaccia utente

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