2008-10-14 13 views
6

Estoy escribiendo una aplicación de WinForms que tiene dos modos: consola o GUI. Tres proyectos dentro de la misma solución, uno para la aplicación de la consola, uno para los formularios de la interfaz de usuario y el tercero para mantener la lógica de que las dos interfaces también se conectarán. La aplicación de consola funciona sin problemas.Extraños errores de UI de cross-threading

Un modelo que mantiene la facilidad de selecciones, tiene una IList<T> donde T es un objeto local, Step, que implementa INotifyPropertyChanged, así que en la interfaz de usuario este está montado en un DataGridView. Todo está bien en el tiempo de ejecución, el estado inicial de los objetos se refleja en la pantalla.

Cada uno de los objetos Step es una tarea que se realiza sucesivamente; algunas de las propiedades cambiarán, se reflejarán en IList y se transmitirán a DataGridView.

Esta acción en las versiones de la interfaz de usuario se realiza mediante la creación de un BackgroundWorker que vuelve a generar eventos en la interfaz de usuario. El Step lo hace y genera un objeto StepResult que es un tipo enumerado que indica un resultado (por ejemplo, Running, NotRun, OK, NotOK, Caveat) y una cadena para indicar un mensaje (porque el paso se ejecutó pero no del modo esperado, es decir, una advertencia). Normalmente las acciones involucrarán una interacción de base de datos, pero en el modo de depuración, genero aleatoriamente un resultado.

Si el mensaje es nulo, no hay nunca un problema, pero si me generan una respuesta como esta:

StepResult returnvalue = new StepResult(stat, "completed with caveat") 

me sale un error que dice que el DataGridView se está accediendo desde un hilo que no sea el hilo se fue creado en. (Estoy pasando esto a través de un controlador personalizado que debe manejar la invocación cuando sea necesario, ¿no es así?)

Luego, si genero una respuesta única, p. utilizando un número aleatorio r:

StepResult returnvalue = new StepResult(stat, r.ToString()); 

las acciones tengan éxito sin ningún problema, los números se escriben limpiamente a la DataGridView.

Estoy desconcertado. Supongo que de alguna manera es un problema literal de cadena, pero ¿alguien puede dar una explicación más clara?

Respuesta

3

Desde que está haciendo la interfaz de usuario de unión a través de suscripción de eventos, you might find this helpful ; es un ejemplo que escribí hace un tiempo que muestra cómo subclase BindingList<T> para que las notificaciones se organicen automáticamente en el subproceso de la interfaz de usuario.

Si no hay contexto de sincronización (es decirmodo consola), luego vuelve a la invocación directa simple, por lo que no hay sobrecarga. Cuando se ejecuta en el subproceso de interfaz de usuario, tenga en cuenta que esto esencialmente utiliza Control.Invoke, que a su vez solo ejecuta el delegado directamente si está en el subproceso de la interfaz de usuario. Por lo tanto, solo existe un cambio si los datos se editan a partir de un hilo que no sea UI: lo que queremos;

+0

No es exactamente la respuesta, ¡pero sí en la línea correcta! – Unsliced

+0

Perfick! Exactamente lo que necesitaba para enlazar a colecciones en una DLL de Winforms y clientes de WPF, gracias. – Wonko

+0

Funciona solo si se creó BindingList en el hilo de la interfaz de usuario, ¿verdad? Pero, ¿hay alguna solución, si BindingList <> se crea fuera del hilo de UI? Y no, no podemos pasar ISynchronizeInvoke o SynchronizationContext correspondiente a BindingList :( –

4

que haya respondido a su propio quesion: -

me sale un error que dice que el DataGridView se está accediendo desde un subproceso distinto del subproceso que se creó el.

WinForms insiste en que todas las acciones realizadas en los formularios y los controles se realizan en el contexto de la rosca de la forma fue creada en. La razón de esto es complejo, pero tiene mucho que ver con la API de Win32 subyacente. Para obtener más información, consulte las diversas entradas en el blog The Old New Thing.

Lo que hay que hacer es usar el InvokeRequired y llamar a los métodos para asegurar que los controles siempre se accede desde el mismo hilo (pseudocodeish):

object Form.SomeFunction (args) 
{ 
    if (InvokeRequired) 
    { 
    return Invoke (new delegate (Form.Somefunction), args); 
    } 
    else 
    { 
    return result_of_some_action; 
    } 
} 
+0

aprecio la situación InvokeRequired, pero ¿por qué funciona cuando paso en una cadena única, pero no cuando se pasa de una serie cableada? – Unsliced

+0

¿Las dos instrucciones se dan desde el mismo lugar en el mismo contexto? – Skizz

+0

Sí. Esos dos ejemplos en la pregunta están en el mismo lugar, ¡si uso un ÉXITO! Si uso el otro - ¡FALLO! Y si el Mensaje no se toca, sino solo el Estado actualizado, entonces no hay errores (pero la Cuadrícula se actualiza). ¡Todavía estoy confundido! – Unsliced

0

me encontré con este artículo - "Updating IBindingList from different thread" - que señaló con el dedo a la BindingList -

Debido a que el BindingList no está configurado para operaciones asíncronas, debe actualizar la BindingList de el mismo hilo sobre el que fue controlado.

pasando explícitamente la forma de los padres como un objeto ISynchronizeInvoke y la creación de un contenedor para el BindingList<T> hizo el truco.

Cuestiones relacionadas