2009-06-10 2 views
7

Ho sofferto per un po 'di tempo: Ho una funzione progettata per aggiungere il controllo a un pannello con gestione cross-thread, il problema è che sebbene il pannello e il controllo sono in "InvokeRequired = false" - Ricevo un'eccezione che mi dice che uno dei controlli interni dei controlli è accessibile da un thread diverso dal thread su cui è stato creato, lo snippet va in questo modo:Eccezione "cross-thread operation not valid" sui controlli interni

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 
    } 

il messaggio di eccezione va in questo modo:

"Cross- operazione thread non valida: controllo 'pnlFoo' si accede da un thread diverso dal thread è stato creato su"

('pnlFoo' è sotto ctrl.Controls)

Come posso aggiungere ctrl al pannello? !


Quando il codice raggiunge "panel.Controls.Add (ctrl);" line - sia panel che ctrl La proprietà "InvokeRequired" è impostata su false, il problema è che i controlli all'interno di ctrl hanno impostato "InvokeRequired" su true. Per chiarire le cose: "pannello" viene creato sul thread di base e "ctrl" sul nuovo thread, quindi, "panel" deve essere invocato (facendo in modo che "ctrl" abbia bisogno di richiamare di nuovo). Una volta che entrambi i richiami sono terminati, raggiunge il comando panel.Controls.Add (ctrl) (sia "panel" che "ctrl" non ha bisogno di invocazione in questo stato)

Ecco un piccolo frammento del completo programma:

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

Ecco il programma: classe pubblica ucFoo: UserControl { panel privato pnlFoo = new Panel(); public ucFoo() { this.Controls.Add (pnlFoo); } } classe pubblica ucFoo2: UserControl { panel privato pnlFooContainer = new Panel(); public ucFoo2() { this.Controls.Add (pnlFooContainer); Thread t = new Thread (new ThreadStartAddFooControlToFooConatiner()); t.Start(); } private AddFooControlToFooConatiner() { ucFoo foo = new ucFoo(); this.pnlFooContainer.Controls.Add (ucFoo); // <- ecco dove viene sollevata l'eccezione } } – Nissim

+0

Ho aggiunto lo snippet come risposta per una lettura migliore – Nissim

risposta

3

Dove si pnlFoo essere creato, e in quale thread? Sai quando viene creato il suo manico? Se viene creato nel thread originale (non-UI), questo è il problema.

Tutti gli handle di controllo nella stessa finestra devono essere creati e aperti sullo stesso thread. A questo punto, non è necessario disporre di due controlli per verificare se Invoke è necessario, poiché ctrl e panel devono utilizzare lo stesso thread.

Se questo non aiuta, si prega di fornire un programma breve ma completo per dimostrare il problema.

+1

Ciò non è vero in realtà. L'oggetto control * * può essere creato su qualsiasi thread, il suo * handle * non può essere creato su un thread diverso. Dal momento che l'OP si sta occupando proprio di questo dettaglio, la differenza è più che rilevante. –

+1

Grazie Remus - verrà modificato. –

+0

(Se si può verificare che la modifica sia corretta, ciò sarebbe di grande aiuto). –

1

Nella propria risposta che affermi:

di chiarire le cose: "pannello" viene creato sul filo di base e "Ctrl" sul nuovo thread

Penso che questo potrebbe essere il causa del tuo problema. Tutti gli elementi dell'interfaccia utente dovrebbero essere creati sullo stesso thread (quello di base). Se hai bisogno di creare "ctrl" come conseguenza di qualche azione nel nuovo thread, quindi lancia un evento nel thread di base e fai la creazione lì.

3

Per inciso - per salvare se stessi dover creare innumerevoli tipi delegato:

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

Inoltre, questo ora non regolari verifiche statiche sulla chiamata interiore per AddControlToPanel, quindi non si può sbagliare.

+1

Provato ma lo stesso errore si presenta Il problema è che una volta richiamato il pannello, ctrl deve essere invocato anche – Nissim

+0

Oh, mi rendo conto che sono d'accordo al 100% con l'analisi di Jon (che in qualche modo stai creando il controllo) su un thread di lavoro) - Stavo solo cercando di mostrare un trucco per salvare un po 'di digitazione e migliorare il controllo statico. –

3

"panel" e "ctrl" devono essere creati sullo stesso thread, ad es. non puoi avere panel.InvokeRequired restituisce un valore diverso da ctrl.InvokeRequired. Questo è se sia il pannello che il ctrl hanno le maniglie create o appartengono a un contenitore con l'handle creato. Da MSDN:

Se la maniglia del controllo non ancora esistere, InvokeRequired ricerche fino a catena padre del controllo finché non trova un controllo o un modulo che ha una maniglia finestra. Se non è possibile trovare un'appropriata maniglia , il metodo InvokeRequired restituisce false.

Come è in questo momento il codice è aperta a condizioni di gara perché il panel.InvokeNeeded può restituire falso perché il pannello non è ancora creata, quindi ctrl.InvokeNeeded tornerò certamente falsa perché molto probabilmente ctrl non è ancora aggiunto a qualsiasi contenitore e quindi quando si raggiunge panel.Controls.Add il pannello è stato creato nel thread principale, quindi la chiamata avrà esito negativo.

+0

Quindi qual è il modo migliore per farlo? – Nissim

+0

Dipende da molti fattori. Puoi anche avere problemi se il pannello viene aggiunto e rimosso dal suo contenitore durante la sua durata (quindi l'handle viene distrutto). Quindi direi che un approccio sicuro sarebbe avere un parametro extra, probabilmente la forma principale, che è garantita per essere creata, e che dovrebbe essere usata per il controllo e il richiamo di thread incrociati. Il thread in background non dovrebbe essere avviato fino a quando non si crea questo modulo. –

+0

BTW Sono anche d'accordo con gli altri post che probabilmente non dovresti fare quello che stai facendo, cercando di creare gli oggetti di controllo nei thread in background e realizzare l'handle nel thread principale. Piuttosto, i thread in background passano * data * al thread principale e creano sempre i controlli sul thread principale. –

1

Ecco un pezzo di lavoro di codice:

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); 
      } 
     } 

Ed ecco come ho provato. È necessario disporre di un modulo con 2 pulsanti e una FlowLayoutPanel (io ho scelto questo modo che io non ha dovuto preoccuparsi di posizione HQuando dinamicamente aggiungendo controlli del pannello)

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()) }); 
     } 

io che lui probem con l'exaple è che quando entri nel ramo InvokeRequired invochi la stessa funzione in cui ti trovi, causando uno strano caso di recurssione.

+0

Provato che ... in realtà è un sinonimo più avanzato del normale richiamo ... ma come ho detto, una volta "pannello" viene invocato - "ctrl" InvokeRequired = true – Nissim

+0

non so cosa dire, funziona bene per me. il tuo codice era esattamente come quello che ho incollato sopra? a volte il più piccolo dettaglio può portare a un errore – AlexDrenea

0

Ecco un piccolo frammento del programma 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

Un sacco di risposte interessanti qui, ma un elemento chiave per qualsiasi multithreading in un'applicazione Winform sta usando il BackgroundWorker di avviare le discussioni, e comunicare di nuovo a il thread principale di Winform.