posts - 315, comments - 268, trackbacks - 15

My Links

News

View Pietro Libro's profile on LinkedIn

DomusDotNet
   DomusDotNet

Pietro Libro

Tag Cloud

Article Categories

Archives

Post Categories

Blogs amici

Links

WPF: System.Windows.Threading.Dispatcher e BeginInvoke

Spesso, quando si lavora con applicazioni multithread (soprattutto in applicazioni Windows Form), siamo abituati a ricorrere alla proprietà InvokeRequierd dei controlli per verificare se il thread chiamante è proprietario del controllo o se lo è un altro, e   nel caso, utilizzare un delegate per utilizzare la proprietà del controllo a cui siamo interessati.  Per un elemento WPF  dobbiamo utilizzare un’istanza di della classe System.Windows.Threading.Dispatcher, presente tra le proprietà che un elemento WPF espone. Ad esempio, supponiamo di utilizzare un’istanza della classe System.Timers.Timer per visualizzare l’ora corrente in un elemento Label:

   1: _timer = new System.Timers.Timer();
   2: _timer.Interval = 1000;
   3: _timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
   4: _timer.Start();

La variabile _timer è dichiarata a livello di modulo nel code-behind di una Window WPF. Se nel gestore dell’evento Elapsed del Timer scrivessimo del codice simile al seguente:

   1: void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
   2: {
   3:     label1.Content = DateTime.Now.ToLongTimeString();            
   4: }

Verrebbe sollevata un’eccezione del tipo InvalidOperationException riportante  il seguente messaggio: “The calling thread cannot access this object because a different thread owns it.” 

Proviamo a scrivere un po’ di codice per utilizzare il Dispatcher:

   1: public delegate void onTickTimerDelegate();        
   2:  
   3: private void UpdateTimeLabel()
   4: {
   5:     if (label1.Dispatcher.Thread == Thread.CurrentThread)
   6:     {
   7:                 
   8:         label1.Content = DateTime.Now.ToLongTimeString();
   9:     }
  10:     else
  11:     {
  12:         onTickTimerDelegate tickTimerDelegate = new onTickTimerDelegate(UpdateTimeLabel);
  13:         label1.Dispatcher.BeginInvoke(tickTimerDelegate,
  14:             System.Windows.Threading.DispatcherPriority.Normal,
  15:             null);
  16:     }
  17: }

Se scriviamo il gestore di  Elapsed in questo modo:

   1: void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
   2: {
   3:     UpdateTimeLabel();
   4: }

Tutto funziona correttamente. Facciamo un ulteriore passo  e modifichiamo il codice in modo che la il nostro metodo (e associato delegate) accetti anche un parametro:

   1: public delegate void onTickTimerDelegateWithParameter(object par);
   2: private void UpdateTimeLabel(object par)
   3: {
   4:     if (label1.Dispatcher.Thread == Thread.CurrentThread)
   5:     {
   6:         label1.Content = DateTime.Now.ToLongTimeString() + " PAR: " + par.ToString();
   7:     }
   8:     else
   9:     {
  10:         onTickTimerDelegateWithParameter tickTimerDelegateWithParameter = new onTickTimerDelegateWithParameter(UpdateTimeLabel);
  11:         label1.Dispatcher.BeginInvoke(tickTimerDelegateWithParameter,
  12:             System.Windows.Threading.DispatcherPriority.Normal,
  13:             par);
  14:     }
  15: }

In questo caso, nella contenuto della label visualizzeremo una stringa rappresentante l’ora  (ore, minuti e secondi) concatenata ad una stringa che “rappresenta” l’oggetto passato come argomento. Ad esempio, se scrivessimo:

   1: void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
   2: {
   3:     UpdateTimeLabel(new Random().Next());
   4: }

Oltre l’ora, verrebbe visualizzato un numero (intero) generato dalla classe Random.

Citando MSDN, la classe Dispatcher rappresenta una coda con priorità dei work item associati ad uno specifico thread. In particolare, in WPF, un oggetto Dispatcher può essere acceduto solo dal relativo  Dispatcher: ad esempio un thread di Background non può essere utilizzato per aggiornare il contenuto di un Button (o una Label come nel caso specifico del post), perché quest’ultimo è associato con il Dispatcher dell’interfaccia utente (sollevando l’eccezione precedentemente descritta). Possiamo quindi utilizzre Invoke e BeginInvoke secondo se vogliamo utilizzare rispettivamente un approccio sincrono o asincrono per l’esecuzione dell’operazione da noi richiesta. Quando aggiungiamo le operazioni ad una coda è importante specificare con quale priorità vogliamo aggiungerla rispetto alle altre operazioni presenti: per far questo utilizziamo l’enum System.Windows.Threading.DispatcherPriority. I valori dell’enum variano da 0 a 10 (in verità da –1 a 10), in ordine di priorità crescente:

  • 0, l’operazione non è processata (Inactive)
  • 1, l’operazione è processata quando il sistema è idle (SystemIdle)
  • 2, l’operazione è processata quanto l’applicazione è idle (ApplicationIdle)
  • 3, l’operazione è processata dopo che le operazioni di background sono state completate (ContextIdle)
  • 4, le operazioni sono processate dopo che tutte le operazioni non-idle sono state completate (Background)
  • 5, le operazioni sono processate con la stessa priorità dell’input (Input)
  • 6, le operazioni sono processate quando il layout ed il render hanno terminato, ma prima delle operazioni Input
  • 7, le operazioni sono processate con la stessa priorità del Render (Render)
  • 8, le operazioni sono processate con la stessa priorità del Data Binding (DataBinding)
  • 9, le operazioni sono processate a priorità normale (tipica dell’applicazione) (Normal)
  • 10, le operazioni sono processare prima delle operazioni asincrone, rappresenta la priorità più alta (Send)

Print | posted on sabato 28 agosto 2010 22:12 | Filed Under [ WPF ]

Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET