2009-06-10 8 views
7

He estado luchando con esto durante bastante tiempo: Tengo una función diseñada para agregar control a un panel con manejo de hilos cruzados, pero el problema es que el panel y el control están en "InvokeRequired = false" - Recibo una excepción diciéndome que se accede a uno de los controles internos desde un hilo que no sea el hilo en el que se creó, el fragmento es así:"Operación de hilo cruzado no válida" excepción en los controles internos

public delegate void AddControlToPanelDlgt(Panel panel, Control ctrl); 
    public void AddControlToPanel(Panel panel, Control ctrl) 
    { 
     if (panel.InvokeRequired) 
     { 
      panel.Invoke(new AddControlToPanelDlgt(AddControlToPanel),panel,ctrl); 
      return; 
     } 
     if (ctrl.InvokeRequired) 
     { 
      ctrl.Invoke(new AddControlToPanelDlgt(AddControlToPanel),panel,ctrl); 
      return; 
     } 
     panel.Controls.Add(ctrl); //<-- here is where the exception is raised 
    } 

el mensaje de excepción es la siguiente:

"Cross- operación hilo no válido: Control 'pnlFoo' se accede desde un subproceso distinto del subproceso que se creó el"

('pnlFoo' está bajo ctrl.Controls)

¿Cómo puedo añadir a ctrl panel? !


Cuando el código llega al "panel.Controles.Agregar (ctrl);" línea - tanto el panel como la propiedad "InvokeRequired" del ctrl están configuradas en falso, el problema es que los controles dentro de ctrl tienen "InvokeRequired" establecido en verdadero. Para aclarar cosas: "panel" se crea en el hilo base y "ctrl" en el nuevo hilo, por lo tanto, se debe invocar "panel" (haciendo que "ctrl" necesite invocar nuevamente). Una vez que se realizan ambas invocaciones, llega al panel.Controles.Agregar (ctrl) comando (tanto "panel" como "ctrl" no necesita invocación en este estado)

Aquí hay un pequeño fragmento del completo programa:

public class ucFoo : UserControl 
{ 
    private Panel pnlFoo = new Panel(); 

    public ucFoo() 
    { 
     this.Controls.Add(pnlFoo); 
    } 
} 

public class ucFoo2 : UserControl 
{ 
    private Panel pnlFooContainer = new Panel(); 

    public ucFoo2() 
    { 
     this.Controls.Add(pnlFooContainer); 
     Thread t = new Thread(new ThreadStart(AddFooControlToFooConatiner()); 
     t.Start() 
    } 

    private AddFooControlToFooConatiner() 
    { 
     ucFoo foo = new ucFoo(); 
     this.pnlFooContainer.Controls.Add(ucFoo); //<-- this is where the exception is raised 
    } 
} 
+0

Aquí está el programa: public class ucFoo: UserControl { private Panel pnlFoo = new Panel(); public ucFoo() { this.Controls.Add (pnlFoo); } } public class ucFoo2: UserControl { private Panel pnlFooContainer = new Panel(); public ucFoo2() { this.Controls.Add (pnlFooContainer); Subproceso t = subproceso nuevo (nuevo ThreadStartAddFooControlToFooConatiner()); t.Start(); } private AddFooControlToFooConatiner() { ucFoo foo = new ucFoo(); this.pnlFooContainer.Controls.Add (ucFoo); // <- aquí es donde se genera la excepción } } – Nissim

+0

Agregué el fragmento como una respuesta para una mejor lectura – Nissim

Respuesta

3

¿Dónde se crea pnlFoo y en qué thread? ¿Sabes cuándo se está creando su mango? Si se está creando en el hilo original (no UI), ese es el problema.

Todos los controles en la misma ventana deben crearse y accederse en el mismo hilo. En ese momento, no debería necesitar dos comprobaciones para determinar si Invoke es necesario, porque ctrl y panel deberían estar usando el mismo hilo.

Si esto no ayuda, proporcione un programa breve pero completo para demostrar el problema.

+1

Eso no es cierto en realidad. El objeto * de control * se puede crear en cualquier subproceso, su * identificador * no se puede crear en un subproceso diferente. Dado que el OP realmente está tratando con este detalle, la diferencia es más que relevante. –

+1

Gracias Remus - editará. –

+0

(Si pudiera comprobar que la edición es correcta, eso sería de gran ayuda.) –

1

En su propia respuesta usted afirma:

Para aclarar las cosas: "panel" se crea en el hilo de base y "Ctrl" en el nuevo hilo

Creo que esto podría ser el causa de tu problema Todos los elementos de la interfaz de usuario se deben crear en el mismo subproceso (el de la base). Si necesita crear "ctrl" como consecuencia de alguna acción en el nuevo hilo, luego reinicie un evento al hilo base y realice la creación allí.

3

Como acotación al margen - para ahorrarse tener que crear un sinnúmero de tipos de delegado:

if (panel.InvokeRequired) 
{ 
    panel.Invoke((MethodInvoker) delegate { AddControlToPanel(panel,ctrl); }); 
    return; 
} 

Además, esto ahora hace comprobaciones regulares estáticas en la llamada interior a AddControlToPanel, por lo que no puede equivocarse.

+1

Intenté eso pero sigue apareciendo el mismo error El problema es que una vez que se invoca el panel, también se debe invocar ctrl – Nissim

+0

Oh, me doy cuenta de que estoy totalmente de acuerdo con el análisis de Jon (que de alguna manera está creando el control) en un hilo de trabajo) - Solo estaba tratando de mostrar un truco para guardar algo de tipeo y mejorar la comprobación estática. –

3

'panel' y 'ctrl' se deben crear en el mismo hilo, es decir. no puede tener el panel. InvokeRequired devuelve un valor diferente que ctrl.InvokeRequired. Eso es si tanto el panel como ctrl tienen los identificadores creados o pertenecen a un contenedor con el identificador creado. De MSDN:

Si el identificador del control hace aún no existir, InvokeRequired searches hasta la cadena principal del control de hasta que encuentra un control o formulario que tiene un mango ventana. Si no se puede encontrar el identificador apropiado, el método InvokeRequired devuelve falso.

Como está ahora su código es abierta a condiciones de carrera porque el panel.InvokeNeeded puede volver falsa ya que el panel no ha sido creada, entonces ctrl.InvokeNeeded sin duda volverá falsa porque lo más probable ctrl aún no se añade a cualquier contenedor y luego cuando llega al panel.Controls.Add el panel se creó en el hilo principal, por lo que la llamada fallará.

+0

Entonces, ¿cuál es la mejor manera de hacerlo? – Nissim

+0

Depende de muchos factores. También puede tener problemas si el panel se agrega y se quita de su contenedor durante su ciclo de vida (por lo que se destruye su identificador). Entonces, yo diría que un enfoque seguro sería tener un parámetro extra, probablemente el formulario principal, que se garantice que se creará, y que debería usarse para la verificación de hilos cruzados e invocar. Su hilo de fondo no debe iniciarse hasta que se cree este formulario. –

+0

BTW También estoy de acuerdo con las otras publicaciones que probablemente no deberías estar haciendo lo que estás haciendo, tratando de crear los objetos de control en los hilos de fondo y realizar el manejo en el hilo principal. Más bien, los hilos de fondo pasan * data * al hilo principal y siempre crean los controles en el hilo principal. –

1

Aquí es una pieza de trabajo de código:

public delegate void AddControlToPanelDlg(Panel p, Control c); 

     private void AddControlToPanel(Panel p, Control c) 
     { 
      p.Controls.Add(c); 
     } 

     private void AddNewContol(object state) 
     { 
      object[] param = (object[])state; 
      Panel p = (Panel)param[0]; 
      Control c = (Control)param[1] 
      if (p.InvokeRequired) 
      { 
       p.Invoke(new AddControlToPanelDlg(AddControlToPanel), p, c); 
      } 
      else 
      { 
       AddControlToPanel(p, c); 
      } 
     } 

Y aquí es cómo lo probé. Es necesario tener un formulario con 2 botones y una FlowLayoutPanel (elegí esto, así que no tiene que preocuparse acerca de la ubicación hCuando dinámicamente la adición de controles en el panel)

private void button1_Click(object sender, EventArgs e) 
     { 
      AddNewContol(new object[]{flowLayoutPanel1, CreateButton(DateTime.Now.Ticks.ToString())}); 
     } 

     private void button2_Click(object sender, EventArgs e) 
     { 
      ThreadPool.QueueUserWorkItem(new WaitCallback(AddNewContol), new object[] { flowLayoutPanel1, CreateButton(DateTime.Now.Ticks.ToString()) }); 
     } 

I que problema tomando con su exaple es que cuando ingresas en la rama InvokeRequired invocas la misma función en la que estás, lo que resulta en un extraño caso de recursión.

+0

Probé que ... en realidad es un sinónimo más avanzado de la invocación normal ... pero como mencioné, una vez que se invoca "panel" - "ctrl" InvokeRequired = true – Nissim

+0

no estoy seguro de qué decir, funciona bien para mí. ¿Estaba tu código exactamente como lo pegué arriba? a veces el más mínimo detalle puede conducir a un error – AlexDrenea

0

Aquí es un pequeño fragmento del programa completo:

public class ucFoo : UserControl 
{ 
    private Panel pnlFoo = new Panel(); 

    public ucFoo() 
    { 
     this.Controls.Add(pnlFoo); 
    } 
} 

public class ucFoo2 : UserControl 
{ 
    private Panel pnlFooContainer = new Panel(); 

    public ucFoo2() 
    { 
     this.Controls.Add(pnlFooContainer); 
     Thread t = new Thread(new ThreadStart(AddFooControlToFooConatiner()); 
     t.Start() 
    } 

    private AddFooControlToFooConatiner() 
    { 
     ucFoo foo = new ucFoo(); 
     this.pnlFooContainer.Controls.Add(ucFoo); //<-- this is where the exception is raised 
    } 
} 
1

muchas respuestas interesantes aquí, pero un elemento clave para cualquier multihilo en una aplicación Winform está utilizando el BackgroundWorker para iniciar las discusiones, y comunicar de nuevo a el hilo principal de Winform.

Cuestiones relacionadas