2009-09-04 11 views
6

Tengo un servicio responsable de muchas tareas, una de las cuales es iniciar trabajos (uno a la vez) en un hilo separado (threadJob child), estos trabajos pueden tomar bastante tiempo y¿Cómo puede un subproceso secundario notificar a un subproceso padre de su estado/progreso?

tienen varias fases a ellos que necesito informarles.

siempre tan a menudo una aplicación que llama solicita el estado del servicio (GetStatus), esto significa que de alguna manera el servicio necesita saber en qué momento el trabajo (hilo hijo) es

en, mi esperanza era que al algunos hitos el subproceso secundario podría de alguna manera informar (SetStatus) el subproceso principal (servicio) de su estado y el servicio podría devolver esa información

a la aplicación que realiza la llamada.

Por ejemplo - que estaba buscando hacer algo como esto:

class Service 
{ 
private Thread threadJob; 
private int JOB_STATUS; 

public Service() 
    { 
    JOB_STATUS = "IDLE"; 
    } 

public void RunTask() 
    { 
    threadJob = new Thread(new ThreadStart(PerformWork)); 
    threadJob.IsBackground = true; 
    threadJob.Start(); 
    } 

public void PerformWork() 
    { 
    SetStatus("STARTING"); 
    // do some work // 
    SetStatus("PHASE I"); 
    // do some work // 
    SetStatus("PHASE II"); 
    // do some work // 
    SetStatus("PHASE III"); 
    // do some work // 
    SetStatus("FINISHED"); 
    } 

private void SetStatus(int status) 
    { 
    JOB_STATUS = status; 
    } 

public string GetStatus() 
    { 
    return JOB_STATUS; 
    } 

}; 

Así, cuando un trabajo se debe realizar runTask() se llama y esto pone en marcha el hilo (threadJob). Esto se ejecutará y realizará algunos pasos (utilizando SetStatus para establecer el nuevo estado en

varios puntos) y finalmente finalizará. Ahora, también existe la función GetStatus() que debe devolver el ESTADO siempre que se solicite (desde una aplicación de llamada usando IPC): este estado

debe reflejar el estado actual de la tarea que ejecuta threadJob.

Entonces, mi problema es bastante simple ... ¿Cómo puede threadJob (o más específicamente, PerformWork()) devolver al servicio el cambio en el estado de manera segura (supongo que mi ejemplo anterior de SetStatus/GetStatus es

inseguro)? ¿Necesito usar eventos? Supongo que no puedo simplemente cambiar JOB_STATUS directamente ... ¿Debo usar un BLOQUEO (si es así en qué?) ...

Respuesta

2

Puede crear un evento en la clase de servicio y luego invocarlo de manera segura para subprocesos. Presta mucha atención a cómo he implementado el método SetStatus.

class Service 
{ 
    public delegate void JobStatusChangeHandler(string status); 

    // Event add/remove auto implemented code is already thread-safe. 
    public event JobStatusHandler JobStatusChange; 

    public void PerformWork() 
    { 
    SetStatus("STARTING"); 
    // stuff 
    SetStatus("FINISHED"); 
    } 

    private void SetStatus(string status) 
    { 
    JobStatusChangeHandler snapshot; 
    lock (this) 
    { 
     // Get a snapshot of the invocation list for the event handler. 
     snapshot = JobStatusChange; 
    } 

    // This is threadsafe because multicast delegates are immutable. 
    // If you did not extract the invocation list into a local variable then 
    // the event may have all delegates removed after the check for null which 
    // which would result in a NullReferenceException when you attempt to invoke 
    // it. 
    if (snapshot != null) 
    { 
     snapshot(status); 
    } 
    } 
} 
+0

No es 100% claro en cuanto a cómo funciona: de su muestra cómo se establece realmente el estado al llamar a SetStatus ... solo se usa con instantánea (estado) pero no hay implementación para el delegado JobStatusChangHandler. Además, ¿cómo funcionaría GetStatus en este caso? – Shaitan00

+0

La persona que llama debe suscribirse al evento JobStatusChange primero. Cuando se llama instantánea (estado), invoca a todos los controladores de eventos. En lugar de sondear el estado a través de GetStatus, la clase de servicio notifica a la persona que llama cada vez que el estado cambia usando un método de evento. –

+0

debe tener cuidado al usar el bloqueo (esto) ya que no sabe quién más intentará bloquear su clase. Esto no es completamente seguro si invita a un punto muerto. –

8

Puede que ya haya investigado esto, pero la clase BackgroundWorker le ofrece una interfaz agradable para ejecutar tareas en subprocesos de fondo, y proporciona eventos para enganchar en las notificaciones que el progreso ha cambiado.

+0

Los eventos son seguros, la mejor manera – Juri

2

Me gustaría que el subproceso secundario planteara un evento 'statusupdate', pasando una estructura con la información necesaria para el padre y que el padre se suscribiera a ella al iniciarla.

0

Me gustaría ir con delegado/evento del hilo a la persona que llama. Si la persona que llama es UI o está en algún lugar en esa línea, me gustaría que se muestre la bomba de mensajes y use Invoke() apropiado para serializar las notificaciones con el subproceso de IU cuando sea necesario.

+0

La persona que llama no es UI, en realidad no hay UI en absoluto en esta aplicación ... – Shaitan00

+0

Entonces no veo el problema con el uso de eventos. –

0

Una vez escribí una aplicación que necesitaba un marcador que mostrara el progreso que estaba haciendo un hilo. Acabo de usar una variable global compartida entre ellos. El padre solo leería el valor, y el hilo simplemente lo actualizaría. No es necesario sincronizar ya que solo el padre lo leyó, y solo el niño lo escribió atómicamente.Como sucedió, el padre estaba redibujando las cosas con la frecuencia suficiente como para que el niño no tuviera que pinchar cuando el niño actualizaba la variable. A veces la forma más simple posible funciona bien.

0

Su código actual mezcla cadenas y entradas para JOB_STATUS, que no pueden funcionar. Estoy asumiendo cadenas aquí, pero en realidad no importa, como explicaré.


Usted implementación actual es seguro para subprocesos en el sentido de que no se produzcan daños en la memoria, ya que todas las asignaciones para hacer referencia a los campos de tipo están garantizados para ser atómica. El CLR lo exige, de lo contrario, podría acceder a la memoria no administrada si pudiera acceder de alguna manera a las referencias parcialmente actualizadas. Sin embargo, su procesador le da esa atomicidad de forma gratuita.

Así que mientras use tipos de referencia como cadenas, no obtendrá ningún daño en la memoria. Lo mismo es cierto para primitivas como ints (y más pequeñas) y enumeraciones basadas en ellas. (Sólo evitar largos y más grandes, y los tipos de valor no simples, como enteros con valores nulos.)

Pero, que no es el final de la historia: esta aplicación no está garantizada para representar siempre el estado actual. La razón de esto es que el hilo que llama a GetStatus podría estar buscando una copia obsoleta del campo JOB_STATUS, porque la asignación en SetState no contiene la denominada barrera de memoria. Es decir: el nuevo valor para JOB_STATUS no necesita ser enviado a su RAM principal de inmediato. Hay varias razones por las que esto puede retrasarse:

  1. escritura a la RAM principal es inherentemente lento (relativamente hablando), que es la razón de su procesador tiene todo tipo de tampones y L-algo cachés en primer lugar, por lo el procesador generalmente retrasa la sincronización de la memoria. No por mucho tiempo, pero probablemente se demorará. Esto puede ser bastante notable en los procesadores multinúcleo, ya que generalmente tienen cachés separados por núcleo.
  2. El JIT podría haber almacenado el valor de JOB_STATUS en un registro anteriormente, como parte de una estrategia de optimización. Nuevamente, los registros son mucho más eficientes de usar que su RAM principal. Sin embargo, esto significa que es posible que no vea los cambios lo suficientemente temprano, ya que todavía está mirando la copia anterior en el registro. (. No estamos hablando de minutos aquí, pero todavía)

lo tanto, si usted quiere estar 100% seguro de que cada núcleo del procesador hilo & es inmediatamente consciente del cambio de estatus, declare su campo como volatile:

private volatile int JOB_STATUS; 

Ahora, GetStatus/SetStatus, sin ningún tipo de constructos de bloqueo, que es verdaderamente hilo de seguridad, como volatile demandas que el valor se lee desde y se escribe en la memoria RAM principal inmediatamente (o algo 100% en equivalentes, si el procesador puede hacer eso más eficientemente).

Tenga en cuenta que si no se declara su campo como volatile debe utilizar primitivas de sincronización, como lock, pero en general es necesario utilizar las primitivas de sincronización tanto Obtener y Conjunto, de lo contrario no va a resolver el problema que volatile corrige.

Tenga en cuenta que, como está haciendo llamadas IPC para obtener el estado, apostaría a que nunca podrá observar ninguna diferencia entre no volátil y volátil, debido a la sobrecarga de las llamadas IPC y las sincronizaciones de hilos sin duda se realizaron detrás de las escenas.

Para obtener más información sobre volatile, vea volatile (C#) en MSDN.

+0

Mi problema ... JOB_STATUS debe ser STRING (no int) ... ¿puedo seguir usando Volatile como lo mencionó? – Shaitan00

+0

sí; los campos de cadena pueden declararse volátiles – Ruben

+0

Es bueno que haya mencionado la palabra clave volátil, sin embargo, la implementación actual no es técnicamente segura sin ella. Claro, las operaciones de lectura/escritura son atómicas, pero el código todavía tiene un problema de barrera de memoria. Hiciste ese punto, pero fue un poco ambiguo para mí. –

Cuestiones relacionadas