Voglio qui riassumere due concetti “base” (nel senso che non li approfondisco molto) riguardo il multithreading con wpf. In particolare due concetti:
- Eseguire componenti UI su thread diversi
- Aggiornare componenti di UI da thread diversi
Per far questo ho scritto una semplice finestra con il seguente contenuto:
<Button Content="Start another Window with same UI Thread" Margin="5,5,5,2" Click="btnSameThread_Click" /> <Button Content="Start another Window with different UI Thread" Margin="5,2,5,2" Click="btnNewThread_Click" /> <Button Content="Perform Long Operation" Margin="5,10,5,10" Click="btnLongOperation_Click" /> <Button Content="Change Textbox of last window opened" Margin="5,5,5,2" Click="btnChangeTextLastWindow_Click" /> <Button Content="Change Textbox of last window opened (secured)" Margin="5,2,5,5" Click="btnChangeTextLastWindowSecured_Click" /> <TextBox Text="Try change this text" x:FieldModifier="public" x:Name="ChangeTextbox" />
Da notare l’ultima textbox con x:FieldModifier=”public”; in questo modo la textbox è accessibile anche dall’esterno della classe (in questo caso non serve, ma l’esempio deriva da un caso in cui il thread era su una classe esterna).
Ecco il codice:
MainWindow lastWindowOpened = null;private void btnSameThread_Click(object sender, RoutedEventArgs e){ lastWindowOpened = new MainWindow(); //show window with same Thread lastWindowOpened.Show(); } private void btnNewThread_Click(object sender, RoutedEventArgs e) { Thread t = new Thread(new ThreadStart(() => { lastWindowOpened = new MainWindow(); //the window is created in a different thread lastWindowOpened.Show(); //show window lastWindowOpened.Closed += (s, ec) => System.Windows.Threading.Dispatcher.CurrentDispatcher.InvokeShutdown(); System.Windows.Threading.Dispatcher.Run(); //start message loop! })); t.SetApartmentState(ApartmentState.STA);//interoperability with com… t.Start(); } private void btnLongOperation_Click(object sender, RoutedEventArgs e) { Thread.Sleep(5000); } private void btnChangeTextLastWindow_Click(object sender, RoutedEventArgs e) { //not safe for UI running on different thread lastWindowOpened.ChangeTextbox.Text = "Cambio da altra window"; } private void btnChangeTextLastWindowSecured_Click(object sender, RoutedEventArgs e) { lastWindowOpened.Dispatcher.BeginInvoke(new Action(() => {//if closed, if not null… lastWindowOpened.ChangeTextbox.Text = "Cambio da altra window"; })); }
Ora occorre capire due concetti:
- btnNewThread esegue un thread in cui mostra una nuova finestra. Il thread tuttavia non ascolta gli eventi della UI finché non si esegue Dispatcher.Run() (http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcher.run.aspx). Eseguendo il metodo statico, il thread corrente entra nel loop dei messaggi finché non si esegue InvokeShutdown(). Cosa succede se non si chiama InvokeShutdown()? La finestra viene chiusa, ma il thread continua ad ascoltare il loop dei messaggi per sempre. Quindi il thread non termina e l’applicazione resta appesa. TaskManager e killiamo
- btnChangeTextLastWindowSecured modifica il contenuto della textbox dell’ultima window aperta. In questo caso si “inietta” un delgate al thread che “possiede” l’elemento di UI. In qusto modo l’operazione viene eseguita dal thread affine alla UI senza creare problemi.
Ora il momento dei test:
- lanciamo la finestra, premiamo il primo bottone per lanciare una nuova window. Ora nella prima premiamo btnLongOperation e muoviamo le finestre…
Non si muovono! questo perchè le due finestre sono gestite dallo stesso thread che viene posto in sleep(). Corretto. - lanciamo la finestra, premiamo il secondo bottone e poi btnLongOperation su una finestra e muoviamo l’altra…
Si muove! questo perchè le due finestre sono su due thread diversi e se si pone un thread in sleep mode, quella finestra è bloccata, ma l’altra no ed è libera di leggere i messaggi dal loop e quindi muovere la window. - riprendiamo il secondo punto. Lanciamo due window su thread diversi e premiamo il 4° button. L’applicazione crasha. Questo perchè si accede ad un elemento di UI appartenente ad un altro thread. L’ultimo button è necessario.
- Ultimo punto, un po’ più complicato…
- Lanciamo una window
- Lanciamo la seconda window su un thread diverso con il secondo button
- sulla seconda window eseguiamo btnLongOperation. La seconda window è quindi bloccata e non si può muovere.
- Ora nella prima window premiamo l’ultimo button… cosa succede?
Niente. Il thread della prima window continua il loop dei messagge ed è libera. La seconda finestra è bloccata. Il testo non cambia fino a che il secondo thread non esce dallo sleep. - Cosa succede se invece di BeginInvoke mettiamo Invoke? In questo caso anche il primo thread si blocca perchè l’invoke è sincrono e attende l’esecuzione dell’altro thread.
Liberi di eseguire altre prove (e postare esempi interessanti)