2010-02-28 29 views
6

Estoy tratando de hacer que mi aplicación C# tenga varios hilos porque a veces recibo una excepción que dice que he llamado a un hilo de una manera insegura. Nunca antes he hecho múltiples subprocesos en un programa, así que tengan paciencia si me parece un poco ignorante sobre el tema.llamadas multihilo en la aplicación Windows Forms?

La visión general de mi programa es que quiero hacer una aplicación de monitoreo de rendimiento. Lo que esto implica es utilizar la clase de contador de proceso y rendimiento en C# para iniciar y controlar el tiempo de procesador de una aplicación, y devolver ese número a la IU. Sin embargo, en el método que realmente llama al método nextValue del contador de rendimiento (que está configurado para funcionar cada segundo gracias a un temporizador), a veces recibo la excepción antes mencionada que habla de llamar a un hilo de una manera insegura.

He adjuntado parte del código para su lectura. Sé que esta es una pregunta que lleva mucho tiempo, por lo que estaría muy agradecido si alguien pudiera ofrecerme ayuda sobre dónde crear un nuevo hilo y cómo llamarlo de manera segura. Traté de ver qué pasaba en MSDN, pero eso me confundió un poco.

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

Respuesta

7

Salida artículo de Jon Skeet en múltiples hilos, en particular la página en multi-threading winforms. Debería arreglarte.

Básicamente debe verificar si se requiere una invocación y luego realizar la invocación si es necesario. Después de leer el artículo que debe ser capaz de refactorizar el código de interfaz de usuario de actualización en bloques que se ven así:

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

En su código, se requerirá una invocación porque está utilizando un temporizador. De acuerdo con la documentación de System.Timers.Timer:

El evento transcurrido se genera en un subproceso ThreadPool.

Esto significa que el método OnTimedEvent() que establece como el delegado del temporizador se ejecutará en el siguiente hilo ThreadPool disponible, lo que sin duda no será el hilo de interfaz de usuario. La documentación también sugiere una forma alternativa de resolver este problema:

Si utiliza el temporizador con un elemento de interfaz de usuario , como una forma o de control, asigne el formulario o control que contiene el temporizador a la SynchronizingObject propiedad, por lo que el evento se calcula para el usuario subproceso de interfaz.

Puede que le resulte más fácil esta ruta, pero no la he probado.

+0

Vale, esto y el comentario trabajador de fondo parece bastante útil; pero, según tengo entendido, el proceso en sí se ejecuta en el hilo de la interfaz de usuario, pero tengo que crear un hilo separado para recopilar y actualizar datos sobre ese proceso. En general, ¿cómo podré decir dónde hacer un hilo por separado? – Waffles

+0

El temporizador ejecutará el delegado ElapsedEventHandler solicitado en el primer subproceso ThreadPool disponible cuando el temporizador "se apaga". Entonces, cualquier cosa que le pida al temporizador que haga se producirá en un hilo separado, no en el hilo de la interfaz de usuario. Agregar un trabajador de fondo simplemente introducirá otro hilo en la ecuación. –

0

Su problema, creo, es que esta línea:

crntreadingslbl.Text = cpuReading.ToString(); 

Está ejecutándose exterior de la rosca de interfaz de usuario. No puede actualizar un elemento de IU fuera del subproceso de IU. Debe llamar a Invoke en la ventana para llamar a un nuevo método en el hilo de UI.

Dicho todo esto, ¿por qué no utilizar perfmon? Está construido para un propósito.

0

El componente BackGroundWorker puede ayudarlo. Está disponible en la caja de herramientas para que pueda arrastrar a su formulario.

Este componente expone un conjunto de eventos para ejecutar tareas en un subproceso diferente al subproceso de la interfaz de usuario. No tiene que preocuparse por crear un hilo.

Toda la interacción entre el código que se ejecuta en segundo plano y los controles de la interfaz de usuario se debe realizar a través de los controladores de eventos.

Para su escenario, puede configurar un temporizador para activar el trabajador de fondo en un intervalo específico.

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

A continuación, poner en práctica los controladores de eventos adecuados para recoger los datos de hecho y actualizar la interfaz de usuario

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