Quando si lavora con i threads in applicazioni Windows vige la regola (col framework 2.0 divenuta finalmente ferrea) di non accedere ad un controllo Windows da threads diversi da quelli che lo hanno creato.
Ho gia' indicato come deve avvenire il tutto con il framework 1.0 e ho da poco scoperto che nel framework 2.0 esiste una classe WindowsFormsSyncronizationContext fatta ad-hoc per questo scopo.
Questa classe, che viene automaticamente valorizzata non appena si invoca Application.Run, si occupa di recuperare automaticamente il controllo sul quale chiamare il metodo Invoke passandogli un SendOrPostCallback delegate.
Ad esempio, per incrementare una progressbar da un thread secondario possiamo scrivere (anche grazie agli anonymous methods di C# 2.0):
using System.Threading;
private void button1_Click(object sender, EventArgs e)
{
Thread t = new Thread(DoWork);
t.Start(WindowsFormsSynchronizationContext.Current);
}
void DoWork(object o)
{
WindowsFormsSynchronizationContext context = (WindowsFormsSynchronizationContext) o;
for (int i = 0; i < 100; i++)
{
context.Send(delegate(object v) { progressBar1.Value = i; }, null);
}
}
In VB2005 il codice e' leggermente diverso:
Imports System.Threading
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim t As New Thread(AddressOf DoWork)
t.Start(WindowsFormsSynchronizationContext.Current)
End Sub
Public Sub DoWork(ByVal o As Object)
Dim context As WindowsFormsSynchronizationContext = DirectCast(o, WindowsFormsSynchronizationContext)
For i As Integer = 0 To 100
context.Send(New SendOrPostCallback(AddressOf UpdateUI), i)
Next
End Sub
Private Sub UpdateUI(ByVal o As Object)
ProgressBar1.Value = Convert.ToInt32(o)
End Sub
Questa classe rende ancora piu' semplice la creazione di componenti che internamente utilizzano threads ma generano eventi senza richiedere marshaling, come fa ad esempio la classe BackgroundWorker.
Esempio:
using System.Threading;
using System.Windows.Forms;
public class SafeEvents
{
SynchronizationContext mContext;
public event EventHandler<ProgressArgs> MyEvent;
public SafeEvents() {mContext = WindowsFormsSynchronizationContext.Current;}
public void Run()
{
Thread t = new Thread(delegate()
{
for (int i = 0; i < 100; i++)
{
if (MyEvent != null)
mContext.Send(delegate(object o){MyEvent(this, new
ProgressArgs(i));},null);
}
});
t.Start();
}
}
public class ProgressArgs:EventArgs
{
public ProgressArgs (int value){this.value=value;}
private int value;
public int Value{get { return value;}}
}
Che posso usare direttamente in questo modo:
SafeEvents evn = new SafeEvents();
evn.MyEvent+= MyEvent;
evn.Run();
void MyEvent(object sender, ProgressArgs e)
{
progressBar1.Value = e.Value;
}