Scenario:
Si vuole eseguire al clic di un certo elemento di un Form un’operazione abbastanza lunga, come per esempio lo scaricamento di alcuni files da internet, ma durante l’operazione l’utente deve poter avere il controllo del programma e avere un feedback visivo della percentuale di completamento dell’operazione.
Soluzione non funzionante:
Ci si rende conto subito che per risolvere un problema del genere è necessario utilizzare un Thread che esegua l’operazione in un contesto separato da quello del Form principale, altrimenti il programma si bloccherà in attesa della fine dell’operazione senza possibilità di ricevere input dall’utente. Il vero problema adesso si crea quando si cerca di aggiornare i componenti del Form dal Thread:

private void button1_Click(object sender, EventArgs e)
{
    testThread = new Thread(threadProc);
    testThread.Start();
}

public void threadProc()
{
    // wast a lot of time...
    for (int i = 1; i <= 10; i++)
    {
        Thread.Sleep(250);
        progressBar.Value = i * 10;
    }
    MessageBox.Show("Operation Completed!");
}

Purtroppo, all’esecuzione della riga di codice dove viene assegnato il valore alla progressBar si otterrà un messaggio di errore simile al seguente:
“Operazione cross-thread non valida: è stato eseguito l’accesso al controllo ‘progressBar1′ da un thread diverso da quello da cui è stata eseguita la creazione.”

lock


Soluzione funzionante
Per far sì che un thread esterno possa accedere agli elementi nel form bisogna invece creare un delegate inserendogli come parametri le informazioni che vogliamo cambiare nel form dal thread, in questo caso per esempio la percentuale di completamento:

public delegate void SetProgressBarDelegate(int value);

Poi scrivere il metodo SetProgressBar da assegnare al delegate:

public void SetProgressBar(int value)
{
	lock (this)
	{
		progressBar1.Value = value;
	}
}

Il lock serve per assicurarsi che il form non sia utilizzato da nessun altro processo (in pratica è come se il codice tra graffe venisse circondato da Monitor.Enter e Monitor.Exit), e ne blocca l’utilizzo da altri thread durante l’esecuzione del codice tra graffe: in questo modo possiamo operare sugli oggetti del form senza correre nessun rischio.
Infine all’interno del thread invochiamo il metodo SetProgressBar:

public void threadProc()
{
	for (int i = 1; i <= 10; i++)
	{
		Thread.Sleep(250);
		this.progressBar1.Invoke(
			new SetProgressBarDelegate(this.SetProgressBar), i * 10);
	}
	MessageBox.Show("Operation Completed!");
}

In questo modo il programma funzionerà correttamente e il thread riuscirà a modificare l’elemento del form senza causare nessuna operazione cross-thread non valida.

Download Form-Thread Interaction. Downloads: 211

Nel download, l’esempio appena descritto in un progetto Visual Studio 2005.


Se sei interessato a questo post, potresti anche provare a leggere:

    No related posts