2012-03-12 7 views
10

Ho una semplice app per winforms che esegue un processo di esecuzione prolungata su un altro thread tramite un'attività TPL. Durante questo lungo processo vorrei aggiornare l'interfaccia utente (la barra di avanzamento o qualcosa del genere). C'è un modo per farlo senza essere obbligato a. Continuare con()?Come aggiornare l'interfaccia utente dalle attività secondarie in WinForms

public partial class Form1 : Form 
{ 
    private Task _childTask; 

    public Form1() 
    { 
     InitializeComponent(); 

     Task.Factory.StartNew(() => 
     { 
      // Do some work 
      Thread.Sleep(1000); 

      // Update the UI 
      _childTask.Start(); 

      // Do more work 
      Thread.Sleep(1000); 
     }); 

     _childTask = new Task((antecedent) => 
     { 
      Thread.Sleep(2000); 
      textBox1.Text = "From child task"; 
     }, TaskScheduler.FromCurrentSynchronizationContext()); 


    } 
} 

L'esecuzione di questo codice ottengo l'eccezione onnipresente:

Cross-thread operation not valid: Control 'textBox1' accessed from a thread other than the thread it was created on.

+0

Penso che si debba incollare il riferimento della casella di testo al thread. – jwillmer

risposta

10

Sì, è possibile chiamare in modo esplicito BeginInvoke sulla Finestra/controllo che si desidera comunicare. Nel tuo caso questo sarebbe simile a questa:

this.textBox.BeginInvoke(new Action(() => 
{ 
    this.textBox.Text = "From child task."; 
})); 
+0

Sembra decisamente in arrivo, ma ottengo un errore di compilazione: impossibile convertire l'espressione lambda in "System.Delegate" perché non è un tipo delegato – devlife

+0

Ho creato un'azione e passato l'azione anziché il metodo anonimo. Non sono sicuro del motivo per cui quanto sopra non funziona, ma mi ha ottenuto il 99% del modo in cui ci sono. Grazie Drew. – devlife

+1

@devlife Oops, è colpa mia essere così abituato a lavorare con le API più recenti. Sì, è necessario includere esplicitamente una nuova azione (...) a causa del modo in cui è stata progettata la firma della vecchia API BeginInvoke di WinForms. Aggiornerò la mia risposta –

5

si sta passando il TaskScheduler come state (o antecedent, come si chiamava). Non ha molto senso.

Non sono sicuro di cosa si voglia esattamente fare, ma è necessario specificare lo TaskScheduler quando si avvia lo Task, non quando lo si sta creando. Inoltre, sembra che childTask non deve essere un campo:

var scheduler = TaskScheduler.FromCurrentSynchronizationContext(); 

Task childTask = null; 

Task.Factory.StartNew(
    () => 
    { 
     Thread.Sleep(1000); 
     childTask.Start(scheduler); 
     Thread.Sleep(1000); 
    }); 

childTask = new Task(
    () => 
    { 
     Thread.Sleep(2000); 
     textBox1.Text = "From child task"; 
    }); 

Naturalmente, tutto questo sta per essere molto più facile con C# 5 e await:

public Form1() 
{ 
    InitializeComponent(); 

    StartAsync(); 
} 

private async void StartAsync() 
{ 
    // do some work 
    await Task.Run(() => { Thread.Sleep(1000); }); 

    // start more work 
    var moreWork = Task.Run(() => { Thread.Sleep(1000); }); 

    // update the UI, based on data from “some work” 
    textBox1.Text = "From async method"; 

    // wait until “more work” finishes 
    await moreWork; 
} 

Non è possibile make async costruttore, ma è possibile eseguire un metodo async da esso. "Doing work" non viene eseguito sul thread dell'interfaccia utente, poiché è stato avviato esplicitamente tramite Task.Run(). Ma poiché StartAsync() è stato chiamato direttamente, viene eseguito sul thread dell'interfaccia utente.

+0

Questo è solo il punto. Non voglio aspettare fino a quando non è stata completata un'attività per eseguire l'altra. – devlife

+0

Ho aggiornato il mio esempio per mostrare meglio ciò che stavo cercando di trasmettere. – devlife

+0

In questo caso, il mio primo campione funzionerà ancora per te. Ho anche aggiornato la versione C# 5. – svick

Problemi correlati