Pregunta

Tengo un UserControl simple para la paginación de base de datos, que usa un controlador para realizar las llamadas DAL reales. Uso un BackgroundWorker para realizar el trabajo pesado, y en el evento OnWorkCompleted vuelvo a habilitar algunos botones, cambio una propiedad TextBox.Text y elevar un evento para el formulario padre.

El formulario A contiene mi UserControl. Cuando hago clic en un botón que abre el formulario B, incluso si no hago nada " allí " y simplemente ciérrelo e intente acceder a la siguiente página desde mi base de datos, se llama al OnWorkCompleted en el subproceso de trabajo (y no a mi subproceso principal), y lanza una excepción de subproceso. p>

En el momento en que agregué un cheque para InvokeRequired en el manejador allí, ¿pero no es el punto completo de OnWorkCompleted ser llamado en el hilo principal? ¿Por qué no funcionaría como se esperaba?

EDITAR:

He logrado reducir el problema a arcgis y BackgroundWorker . Tengo la siguiente solución que agrega un comando a arcmap, que abre un simple Form1 con dos botones.

El primer botón ejecuta un BackgroundWorker que duerme durante 500 ms y actualiza un contador. En el método RunWorkerCompleted comprueba InvokeRequired , y actualiza el título para mostrar si el método se estaba ejecutando originalmente dentro del subproceso principal o el subproceso de trabajo. El segundo botón simplemente abre Form2 , que no contiene nada.

Al principio, todas las llamadas a RunWorkerCompletedare se realizan dentro del subproceso principal (Como se esperaba, ese es el punto en el que se encuentra el método RunWorkerComplete, al menos por lo que entiendo de MSDN en BackgroundWorker )

Después de abrir y cerrar Form2 , siempre se llama al RunWorkerCompleted en el subproceso de trabajo. Quiero agregar que puedo dejar esta solución al problema tal como está (verifique InvokeRequired en el método RunWorkerCompleted ), pero quiero entender por qué sucede en contra mis expectativas. En mi " real " código Me gustaría saber siempre que se llama al método RunWorkerCompleted en el subproceso principal.

Logré identificar el problema en el form.Show (); comando en mi BackgroundTesterBtn - si uso ShowDialog () en su lugar, no tengo ningún problema ( RunWorkerCompleted siempre se ejecuta en el hilo principal). Necesito usar Show () en mi proyecto de ArcMap, para que el usuario no esté vinculado al formulario.

También intenté reproducir el error en un proyecto normal de WinForms. Agregué un proyecto simple que solo abre el primer formulario sin ArcMap, pero en ese caso no pude reproducir el error: el RunWorkerCompleted se ejecutó en el hilo principal, si usé Show () o ShowDialog () , antes y después de abrir Form2 . Intenté agregar una tercera forma para actuar como formulario principal antes de mi Form1 , pero no cambió el resultado.

Aquí está mi sln simple (VS2005sp1) - requiere

ESRI.ArcGIS.ADF (9.2.4.1420)

ESRI.ArcGIS.ArcMapUI (9.2.3.1380)

ESRI.ArcGIS.SystemUI (9.2.3.1380)

¿Fue útil?

Solución

Parece un error:

http://connect.microsoft.com/VisualStudio/feedback /ViewFeedback.aspx?FeedbackID=116930

http://thedatafarm.com/devlifeblog/archive/2005 /12/21/39532.aspx

Así que sugiero utilizar el bullet-proof (pseudocódigo):

if(control.InvokeRequired)
  control.Invoke(Action);
else
  Action()

Otros consejos

  

¿No se debe llamar a todo el punto de OnWorkCompleted en el hilo principal? ¿Por qué no funcionaría como se esperaba?

No, no lo es.
No puedes simplemente correr cualquier cosa vieja en un hilo viejo. Los hilos no son objetos corteses que simplemente puede decir " ejecutar esto, por favor " ;.

Un mejor modelo mental de un hilo es un tren de carga. Una vez que va, se va en su propia pista. No puedes cambiar su rumbo o detenerlo. Si desea influir en él, debe esperar hasta que llegue a la siguiente estación de tren (p. Ej., Verificarlo en algunos eventos), o descarrilarlo ( Thread.Abort y las excepciones CrossThread tienen más o menos las mismas consecuencias que descarrilar un tren ... ¡cuidado!).

Los

controles de Winforms tipo de admiten este comportamiento (tienen Control.BeginInvoke que te permite ejecutar cualquier función en el subproceso de la interfaz de usuario), pero eso solo funciona porque tienen un Enganche especial en la bomba de mensajes de la interfaz de usuario de Windows y escriba algunos controladores especiales. Para ir con la analogía anterior, su tren se registra en la estación y busca nuevas direcciones periódicamente, y usted puede usar esa instalación para publicarla según sus propias instrucciones.

El BackgroundWorker está diseñado para ser de propósito general (no puede vincularse a la GUI de Windows), por lo que no puede usar las funciones de Control.BeginInvoke de windows. Tiene que asumir que su hilo principal es un 'tren' imparable que hace sus propias cosas, por lo que el evento completado debe ejecutarse en el hilo de trabajo o no hacerlo en absoluto.

Sin embargo, mientras utiliza winforms, en su controlador OnWorkCompleted , puede hacer que la ventana ejecute la devolución de llamada de otra usando el BeginInvoke Funcionalidad que mencioné anteriormente. Así:

// Assume we're running in a windows forms button click so we have access to the 
// form object in the "this" variable.
void OnButton_Click(object sender, EventArgs e )
    var b = new BackgroundWorker();
    b.DoWork += ... blah blah

    // attach an anonymous function to the completed event.
    // when this function fires in the worker thread, it will ask the form (this)
    // to execute the WorkCompleteCallback on the UI thread.
    // when the form has some spare time, it will run your function, and 
    // you can do all the stuff that you want
    b.RunWorkerCompleted += (s, e) { this.BeginInvoke(WorkCompleteCallback); }
    b.RunWorkerAsync(); // GO!
}

void WorkCompleteCallback()
{
    Button.Enabled = false;
    //other stuff that only works in the UI thread
}

Además, no olvide esto:

  

Su controlador de eventos RunWorkerCompleted siempre debe verificar las propiedades Error y Cancelado antes de acceder a la propiedad Resultado. Si se generó una excepción o si se canceló la operación, el acceso a la propiedad Resultado genera una excepción.

El BackgroundWorker verifica si la instancia delegada apunta a una clase que admita la interfaz ISynchronizeInvoke . Su capa DAL probablemente no implemente esa interfaz. Normalmente, usaría el BackgroundWorker en un Form , que es compatible con esa interfaz.

En caso de que desee utilizar el BackgroundWorker de la capa DAL y desee actualizar la IU desde allí, tiene tres opciones:

  • te quedarías llamando al método Invoke
  • implemente la interfaz ISynchronizeInvoke en la clase DAL y redirija las llamadas manualmente (solo son tres métodos y una propiedad)
  • antes de invocar el BackgroundWorker (por lo tanto, en el subproceso de la IU), para llamar a SynchronizationContext.Current y para guardar la instancia de contenido en una variable de instancia. El SynchronizationContext le proporcionará el método Send , que hará exactamente lo que hace Invoke .

El mejor enfoque para evitar problemas con los enlaces cruzados en la GUI es use SynchronizationContext .

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top