2015-08-07 11 views
6

Ho appena risposto alla domanda se un Task può aggiornare l'interfaccia utente. Mentre giocavo con il mio codice, mi sono reso conto di non essere chiaro su alcune cose.Sfondo Attività a volte è in grado di aggiornare l'interfaccia utente?

Se ho un Windows Form con un controllo txtHello su di esso, sono in grado di aggiornare l'interfaccia utente da un task, a quanto pare, se ho subito faccio su Task.Run:

public partial class Form1 : Form 
{ 
    public Form1() 
    { 
     InitializeComponent(); 
     Task.Run(() => 
     { 
      txtHello.Text = "Hello"; 
     }); 
    } 
} 

Tuttavia se io Thread.Sleep anche per 5 millesimi di secondo, viene generata l'errore previsto CrossThread:

public partial class Form1 : Form 
{ 
    public Form1() 
    { 
     InitializeComponent(); 
     Task.Run(() => 
     { 
      Thread.Sleep(5); 
      txtHello.Text = "Hello"; //kaboom 
     }); 
    } 
} 

io non sono sicuro del perché ciò accade. Esiste una sorta di ottimizzazione per un funzionamento estremamente breve Task?

+0

Questa sarà una sorpresa anche per me se è vera! –

risposta

6

Non ho postato la traccia dello stack eccezione, ma mi aspetto che sembrava qualcosa di simile:

System.InvalidOperationException: Cross-thread operation not valid: Control 'textBox1' accessed from a thread other than the thread it was created on. 
    at System.Windows.Forms.Control.get_Handle() 
    at System.Windows.Forms.Control.set_WindowText(String value) 
    at System.Windows.Forms.TextBoxBase.set_WindowText(String value) 
    at System.Windows.Forms.Control.set_Text(String value) 
    at System.Windows.Forms.TextBoxBase.set_Text(String value) 
    at System.Windows.Forms.TextBox.set_Text(String value) 
    at WindowsFormsApplicationcSharp2015.Form1.<.ctor>b__0_0() in D:\test\WindowsFormsApplicationcSharp2015\Form1.cs:line 27 

possiamo vedere che l'eccezione viene generata dalla proprietà Control.Handle getter. E infatti, se guardiamo la source code per la proprietà, non v'è, come ci si aspettava:

public IntPtr Handle { 
    get { 
     if (checkForIllegalCrossThreadCalls && 
      !inCrossThreadSafeCall && 
      InvokeRequired) { 
      throw new InvalidOperationException(SR.GetString(SR.IllegalCrossThreadCall, 
                  Name)); 
     } 

     if (!IsHandleCreated) 
     { 
      CreateHandle(); 
     } 

     return HandleInternal; 
    } 
} 

La parte interessante è quando guardiamo il codice che chiama Control.Handle. In questo caso, questa è la proprietà Control.WindowText setter:

set { 
    if (value == null) value = ""; 
    if (!WindowText.Equals(value)) { 
     if (IsHandleCreated) { 
      UnsafeNativeMethods.SetWindowText(new HandleRef(window, Handle), value); 
     } 
     else { 
      if (value.Length == 0) { 
       text = null; 
      } 
      else { 
       text = value; 
      } 
     } 
    } 
} 

Si noti che la proprietà Handle viene richiamato solo se è IsHandleCreatedtrue.

E per completezza, se guardiamo il codice per IsHandleCreated vediamo quanto segue:

public bool IsHandleCreated { 
    get { return window.Handle != IntPtr.Zero; } 
} 

Quindi, la ragione per cui non si ottiene l'eccezione, è perché per il momento le Task eseguita, il handle della finestra non è stato ancora creato, il che è prevedibile dal momento che il Task inizia nel costruttore del form, cioè prima che il modulo venga visualizzato.

Prima che venga creato l'handle della finestra, la modifica di una proprietà non richiede ancora alcun intervento dal thread dell'interfaccia utente. Quindi, durante questa piccola finestra temporale all'inizio del programma, sembrerebbe che sia possibile richiamare i metodi sulle istanze di controllo da un thread non dell'interfaccia utente senza ottenere l'eccezione "cross thread". Ma chiaramente, l'esistenza di questa piccola finestra temporale speciale non cambia il fatto che dovremmo sempre assicurarci di invocare metodi di controllo dal thread dell'interfaccia utente per essere sicuri.

Per dimostrare che il tempo della creazione della maniglia della finestra è il fattore determinante nell'ottenere (o meno) l'eccezione "cross thread", provare a modificare l'esempio per forzare la creazione dell'handle della finestra prima di iniziare l'attività , e notare come si intende ora ottenere costantemente l'eccezione prevista, anche in assenza di un sonno:

public partial class Form1 : Form 
{ 
    public Form1() 
    { 
     InitializeComponent(); 

     // Force creation of window handle 
     var dummy = txtHello.Handle; 

     Task.Run(() => 
     { 
      txtHello.Text = "Hello"; // kaboom 
     }); 
    } 
} 

documentazione pertinente: Control.Handle

Se la maniglia non è ancora stato creato, fa riferimento a questa proprietà forzerà il gestire per essere creato.

Problemi correlati