2010-02-16 22 views
5

Estoy haciendo un parche para resolver un problema de barra de progreso en una aplicación que está un poco desordenada. La cancelación en la barra de progreso solía hacer un Thread.Abort en el hilo haciendo el trabajo pesado. Cambié eso a levantar una bandera de cancelación que puedo verificar en el lugar estratégico en el hilo.¿Cómo evitar pasar hambre con el hilo principal?

La mayoría de las veces funciona bien, pero de vez en cuando la cancelación no funciona en absoluto. Supongo que podría hacer un Application.DoEvents antes de mirar el estado de la bandera (no hay riesgo de reingreso) pero me gustaría una opción más "limpia".

Agradecería que alguien me pudiera proporcionar información para comprender qué está sucediendo exactamente y cómo funciona esto detrás de la escena. Me gustaría saber cómo tratar este problema sin utilizar el BackgroundWorker (como lo haría en .net 1.1) pero también me gustaría saber si el BackgroundWorker resuelve ese tipo de problemas y cómo lo hace.

Editar: Estoy tomando notas de sus sugerencias e intentaré algo mañana e informaré. Al principio, utilicé un bool volátil porque creo que lo actualicé en una propiedad automática y me olvidé de lo volátil. ¿Podría el hilo del trabajador seguir buscando el valor en caché una y otra vez? No veo cómo podría tener un deathlock. El trabajador para verificar la bandera desde que logré romper allí colocando un punto de quiebre sobre la marcha. Siempre pruebo con el mismo conjunto de datos y la mayoría de las veces cancela muy bien. Lo único que cambia entre las pruebas es el momento en que presiono cancelar. Hasta ahora, solo probé en depuración, comencé desde VS.

Editar 2: Resulta que mi problema no está relacionado con mi bandera o cualquier cosa que agregué. Es más un problema de WinForm. El programa puede llamar al ShowDialog (y ya hay otro ShowDialog bloqueado). No puedo arrastrar el formulario y no se actualiza por sí mismo. El botón de cancelar no funciona. Aquí está la pila de llamadas cuando pause todo.

 
[Code externe] 
    Mrnf.Son.Commun.dll!Mrnf.Son.Commun.Messages.BarreProgressionBase.ShowDialog(System.Windows.Forms.IWin32Window fenetre = {Mrnf.Son.Presentation.Windows.UI.Echanges.AssistantForm}) Ligne 274 + 0xb octets C# 
    Mrnf.Son.Commun.dll!Mrnf.Son.Commun.Controleurs.Utils.AttendreFinTraitement(System.Windows.Forms.Form parent = {Mrnf.Son.Presentation.Windows.UI.Echanges.AssistantForm}, Mrnf.Son.Commun.Messages.BarreProgressionBase progressionBase = {Mrnf.Son.Commun.Messages.BarreProgressionMessage}, System.Threading.Thread thread = {System.Threading.Thread}) Ligne 302 + 0xd octets C# 
    Mrnf.Son.Affaires.dll!Mrnf.Son.Affaires.Persisteurs.Echanges.LecteurDBFGeneriqueCollection.Importer(System.Windows.Forms.Form parent = {Mrnf.Son.Presentation.Windows.UI.Echanges.AssistantForm}) Ligne 95 + 0x1d octets C# 
    Mrnf.Son.Affaires.dll!Mrnf.Son.Affaires.Persisteurs.Echanges.PersisteurModeleEchanges.Importer(Mrnf.Son.Affaires.Entites.Echanges.ModeleEchanges unModele = {Mrnf.Son.Presentation.Windows.Controleurs.Echanges.ModeleEchanges.ModeleEchangesGenerique}, System.Windows.Forms.Form formParent = {Mrnf.Son.Presentation.Windows.UI.Echanges.AssistantForm}) Ligne 1880 + 0xd octets C# 
    Mrnf.Son.Affaires.dll!Mrnf.Son.Affaires.Entites.Echanges.ModeleEchanges.Importer(System.Windows.Forms.Form formParent = {Mrnf.Son.Presentation.Windows.UI.Echanges.AssistantForm}) Ligne 875 + 0x18 octets C# 
    Mrnf.Son.Presentation.Windows.exe!Mrnf.Son.Presentation.Windows.UI.Echanges.AssistantForm.EffectuerImport(Mrnf.Son.Affaires.Entites.Echanges.IModeleEchanges modele = {Mrnf.Son.Presentation.Windows.Controleurs.Echanges.ModeleEchanges.ModeleEchangesGenerique}) Ligne 1429 + 0xc octets C# 
    Mrnf.Son.Presentation.Windows.exe!Mrnf.Son.Presentation.Windows.UI.Echanges.AssistantForm._terminerBtn_Click(object sender = {Text = Impossible d'évaluer l'expression, car un frame natif se trouve en haut de la pile des appels.}, System.EventArgs e = {System.EventArgs}) Ligne 1334 + 0x1d octets C# 
[Code externe] 
    Mrnf.Son.Presentation.Windows.exe!Mrnf.Son.Presentation.Windows.UI.Echanges.AssistantForm.WndProc(ref System.Windows.Forms.Message m = {System.Windows.Forms.Message}) Ligne 1133 + 0xb octets C# 
[Code externe] 
    Mrnf.Son.Presentation.Windows.exe!Mrnf.Son.Presentation.Windows.Controleurs.Sondages.ActionsSondages.OnImporterSysExt() Ligne 1362 + 0x1f octets C# 
    Mrnf.Son.Presentation.Windows.exe!Mrnf.Son.Presentation.Windows.UI.Sondages.UEExploitationVue._mniImporterSysExt_Click(object sender = {System.Windows.Forms.ToolStripMenuItem}, System.EventArgs e = {System.EventArgs}) Ligne 820 + 0x12 octets C# 
[Code externe] 
    Mrnf.Son.Presentation.Windows.exe!Mrnf.Son.Presentation.Windows.Program.Main() Ligne 148 + 0x8 octets C# 
[Code externe] 

Datos 3: Si paso null a la ShowDialog funciona bien (la interfaz de usuario no se congela, el funcionamiento del botón cancelar, se cancela fina). Realmente no entiendo la magia detrás de todo esto.

Respuesta

1

Las otras publicaciones probablemente estén bien encaminadas.

Me gusta usar WaitHandles junto con Thread.Join()/Thread.Abort() cuando intento matar un hilo.

private readonly ManualResetEvent _ExitThreadsEvent = new ManualResetEvent(false); 
private Thread _MyThread; 

public void Stop() 
{ 
    _ExitThreadsEvent.Set(); 

    if (_MyThread != null) 
    { 
     if (!_MyThread.Join(5000)) 
     { 
      _MyThread.Abort(); 
     } 

     _MyThread = null; 
    } 
} 

private void MyThread() 
{ 
    if (!_ExitThreadsEvent.WaitOne(1)) 
    { 
     // Do some work... 
    } 

    if (!_ExitThreadsEvent.WaitOne(1)) 
    { 
     // Do some more work... 
    } 
} 

Probablemente sea bueno descubrir el problema original de un interbloqueo.

+0

¿Hay alguna ventaja de usar 'ManuelResetEvent' sobre una bandera' volátil' en esa situación? Además de la posibilidad de bloquear si es necesario (que no), no veo ninguno. –

+0

Lo uso porque está específicamente diseñado para señalar uno o más hilos y no toma mucha consideración para implementarlo correctamente. En general, es probablemente "más pesado" que una bandera volátil. Ambos funcionarán. Prefiero uno sobre el otro :-) –

+0

Ok. Mi problema es claramente un punto muerto no relacionado con mi modificación. Me interrumpe al llamar 'ShowDialog' en un clic de botón ya en un diálogo. Supongo que la llamada múltiple a ShowDialog es mala. Editar: esperamos bloquear en esos ShowDialog, pero aún así poder procesar eventos como el botón cancelar en él. –

4

BackgroundWorker no hace nada extra aquí, excepto para proporcionar esa bandera en una ubicación fácil de verificar. Así que hay algunas posibilidades:

  • su código es cada vez para comprobar la bandera, pero sin darse cuenta de que ha cambiado (teóricamente posible si la bandera no está sincronizado o volátil, pero muy poco probable en el código no trivial)
  • su código no está recibiendo para verificar la bandera

Vamos a suponer que este último; algunas de las causas comunes de que:

  • está accidentalmente DEADLOCKING mismo (tal vez tratando de Invoke de nuevo al hilo de interfaz de usuario que ya está a la espera de un subproceso de fondo, o entrar en un bucle alrededor de una cerradura o similar)
  • que está llamando a lo largo de COM (o similar), y que está completando llamada nunca - un escenario particularmente difícil escapar de en algunos casos

¿se puede reducir cuál de estos es?Tal vez inserte algún código para rastrear lo que su hilo está haciendo a intervalos, y asegúrese de que está haciendo algo útil, o si no, realice un seguimiento de dónde se está atascando.

2

Application.DoEvents es un medio para permitir que se procesen los eventos pendientes en la bomba de mensajes. Normalmente no debería tener absolutamente nada que ver con el hilo de fondo.

Si la cancelación 'no funciona en absoluto', la solución dependerá en gran medida de lo que 'no funciona en absoluto'. ¿No puedes cambiar la bandera? ¿Está atascada la interfaz de usuario? ¿El hilo de fondo no responde al chane de la bandera? ¿Es algo más? La solución depende principalmente de cuál es exactamente el problema. Puede ser que no estés revisando la bandera desde el fondo, podría ser que bloqueas los dos hilos. Mostrar el código o elaborar los detalles del problema ayudaría.

2

Esto es casi siempre fácil de depurar. Cuando vea el hilo del trabajador ignorando la solicitud de cancelación, use Depurar + Romper todo. A continuación, haga clic en Debug + Windows + Threads y haga doble clic en el hilo de trabajo. Luego mire la pila de llamadas para ver qué está haciendo el hilo y por qué no pasa el código que verifica el indicador.

Tenga en cuenta que debe declarar el miembro de la bandera con la palabra clave volátil. Esto evita que el compilador JIT genere código de máquina que carga el valor del miembro en un registro y nunca verifica el valor real de la variable en la memoria. Que es probable que suceda cuando ejecuta la versión de lanzamiento de su programa sin un depurador. En ese caso, asegúrese de usar Herramientas + Adjuntar a proceso para adjuntar el depurador antes de usar el comando Romper todo.

A ManualResetEvent, marcado con una llamada WaitOne (0) es mejor.

Cuestiones relacionadas