Procedura: effettuare chiamate thread-safe a controlli di Windows Form

Aggiornamento: novembre 2007

Se si utilizza il multithreading per migliorare le prestazioni delle applicazioni Windows Form, è necessario essere sicuri che le chiamate ai controlli siano thread-safe.

L'accesso ai controlli di Windows Form di per sé non è thread-safe. Se sono presenti due o più thread che gestiscono lo stato di un controllo, è possibile che il controllo venga forzato in uno stato incoerente. Possono inoltre verificarsi altri errori connessi ai thread, inclusi race condition e deadlock. È importante accertarsi che l'accesso ai controlli venga reso thread-safe.

.NET Framework consente di rilevare l'accesso ai controlli in modalità non thread-safe. Quando l'applicazione viene eseguita nel debugger e un thread diverso da quello che ha creato il controllo tenta di chiamare quel controllo, il debugger genera un'eccezione InvalidOperationException con il messaggio "È stato eseguito l'accesso al controllo nome controllo da un thread diverso da quello da cui è stata eseguita la creazione".

Questa eccezione si verifica molto probabilmente durante il debug e, in alcune circostanze, in fase di esecuzione. Se si presenta questo problema, si consiglia di correggerlo. Questa eccezione potrebbe verificarsi durante il debug di applicazioni scritte con una versione di .NET Framework precedente alla 2.0.

Nota:

Per disattivare l'eccezione, impostare il valore della proprietà CheckForIllegalCrossThreadCalls su false per eseguire controllo come in Visual Studio 2003.

Nell'esempio di codice riportato di seguito viene illustrato come chiamare i controlli Windows Form in modalità thread-safe e non thread-safe da un thread di lavoro. Vengono illustrate una modalità di impostazione non thread-safe della proprietà Text di un controllo TextBox e due modalità di impostazione thread-safe della proprietà Text.

using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;

namespace CrossThreadDemo
{
	public class Form1 : Form
	{
		// This delegate enables asynchronous calls for setting
		// the text property on a TextBox control.
		delegate void SetTextCallback(string text);

		// This thread is used to demonstrate both thread-safe and
		// unsafe ways to call a Windows Forms control.
		private Thread demoThread = null;

		// This BackgroundWorker is used to demonstrate the 
		// preferred way of performing asynchronous operations.
		private BackgroundWorker backgroundWorker1;

		private TextBox textBox1;
		private Button setTextUnsafeBtn;
		private Button setTextSafeBtn;
		private Button setTextBackgroundWorkerBtn;

		private System.ComponentModel.IContainer components = null;

		public Form1()
		{
			InitializeComponent();
		}

		protected override void Dispose(bool disposing)
		{
			if (disposing && (components != null))
			{
				components.Dispose();
			}
			base.Dispose(disposing);
		}

		// This event handler creates a thread that calls a 
		// Windows Forms control in an unsafe way.
		private void setTextUnsafeBtn_Click(
			object sender, 
			EventArgs e)
		{
			this.demoThread = 
				new Thread(new ThreadStart(this.ThreadProcUnsafe));

			this.demoThread.Start();
		}

		// This method is executed on the worker thread and makes
		// an unsafe call on the TextBox control.
		private void ThreadProcUnsafe()
		{
			this.textBox1.Text = "This text was set unsafely.";
		}

		// This event handler creates a thread that calls a 
		// Windows Forms control in a thread-safe way.
		private void setTextSafeBtn_Click(
			object sender, 
			EventArgs e)
		{
			this.demoThread = 
				new Thread(new ThreadStart(this.ThreadProcSafe));

			this.demoThread.Start();
		}

		// This method is executed on the worker thread and makes
		// a thread-safe call on the TextBox control.
		private void ThreadProcSafe()
		{
			this.SetText("This text was set safely.");
		}

		// This method demonstrates a pattern for making thread-safe
		// calls on a Windows Forms control. 
		//
		// If the calling thread is different from the thread that
		// created the TextBox control, this method creates a
		// SetTextCallback and calls itself asynchronously using the
		// Invoke method.
		//
		// If the calling thread is the same as the thread that created
		// the TextBox control, the Text property is set directly. 

		private void SetText(string text)
		{
			// InvokeRequired required compares the thread ID of the
			// calling thread to the thread ID of the creating thread.
			// If these threads are different, it returns true.
			if (this.textBox1.InvokeRequired)
			{	
				SetTextCallback d = new SetTextCallback(SetText);
				this.Invoke(d, new object[] { text });
			}
			else
			{
				this.textBox1.Text = text;
			}
		}

		// This event handler starts the form's 
		// BackgroundWorker by calling RunWorkerAsync.
		//
		// The Text property of the TextBox control is set
		// when the BackgroundWorker raises the RunWorkerCompleted
		// event.
		private void setTextBackgroundWorkerBtn_Click(
			object sender, 
			EventArgs e)
		{
			this.backgroundWorker1.RunWorkerAsync();
		}
		
		// This event handler sets the Text property of the TextBox
		// control. It is called on the thread that created the 
		// TextBox control, so the call is thread-safe.
		//
		// BackgroundWorker is the preferred way to perform asynchronous
		// operations.

		private void backgroundWorker1_RunWorkerCompleted(
			object sender, 
			RunWorkerCompletedEventArgs e)
		{
			this.textBox1.Text = 
				"This text was set safely by BackgroundWorker.";
		}

		#region Windows Form Designer generated code

		private void InitializeComponent()
		{
			this.textBox1 = new System.Windows.Forms.TextBox();
			this.setTextUnsafeBtn = new System.Windows.Forms.Button();
			this.setTextSafeBtn = new System.Windows.Forms.Button();
			this.setTextBackgroundWorkerBtn = new System.Windows.Forms.Button();
			this.backgroundWorker1 = new System.ComponentModel.BackgroundWorker();
			this.SuspendLayout();
			// 
			// textBox1
			// 
			this.textBox1.Location = new System.Drawing.Point(12, 12);
			this.textBox1.Name = "textBox1";
			this.textBox1.Size = new System.Drawing.Size(240, 20);
			this.textBox1.TabIndex = 0;
			// 
			// setTextUnsafeBtn
			// 
			this.setTextUnsafeBtn.Location = new System.Drawing.Point(15, 55);
			this.setTextUnsafeBtn.Name = "setTextUnsafeBtn";
			this.setTextUnsafeBtn.TabIndex = 1;
			this.setTextUnsafeBtn.Text = "Unsafe Call";
			this.setTextUnsafeBtn.Click += new System.EventHandler(this.setTextUnsafeBtn_Click);
			// 
			// setTextSafeBtn
			// 
			this.setTextSafeBtn.Location = new System.Drawing.Point(96, 55);
			this.setTextSafeBtn.Name = "setTextSafeBtn";
			this.setTextSafeBtn.TabIndex = 2;
			this.setTextSafeBtn.Text = "Safe Call";
			this.setTextSafeBtn.Click += new System.EventHandler(this.setTextSafeBtn_Click);
			// 
			// setTextBackgroundWorkerBtn
			// 
			this.setTextBackgroundWorkerBtn.Location = new System.Drawing.Point(177, 55);
			this.setTextBackgroundWorkerBtn.Name = "setTextBackgroundWorkerBtn";
			this.setTextBackgroundWorkerBtn.TabIndex = 3;
			this.setTextBackgroundWorkerBtn.Text = "Safe BW Call";
			this.setTextBackgroundWorkerBtn.Click += new System.EventHandler(this.setTextBackgroundWorkerBtn_Click);
			// 
			// backgroundWorker1
			// 
			this.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted);
			// 
			// Form1
			// 
			this.ClientSize = new System.Drawing.Size(268, 96);
			this.Controls.Add(this.setTextBackgroundWorkerBtn);
			this.Controls.Add(this.setTextSafeBtn);
			this.Controls.Add(this.setTextUnsafeBtn);
			this.Controls.Add(this.textBox1);
			this.Name = "Form1";
			this.Text = "Form1";
			this.ResumeLayout(false);
			this.PerformLayout();

		}

		#endregion


		[STAThread]
		static void Main()
		{
			Application.EnableVisualStyles();
			Application.Run(new Form1());
		}

	}
}


Chiamate non thread-safe a un controllo di Windows Form

Le chiamate a un controllo di Windows Form direttamente da un thread di lavoro non sono thread-safe. Durante il debug dell'applicazione il debugger genera un'eccezione InvalidOperationException per segnalare le chiamate ai controlli non thread-safe.

		// This event handler creates a thread that calls a 
		// Windows Forms control in an unsafe way.
		private void setTextUnsafeBtn_Click(
			object sender, 
			EventArgs e)
		{
			this.demoThread = 
				new Thread(new ThreadStart(this.ThreadProcUnsafe));

			this.demoThread.Start();
		}

		// This method is executed on the worker thread and makes
		// an unsafe call on the TextBox control.
		private void ThreadProcUnsafe()
		{
			this.textBox1.Text = "This text was set unsafely.";
		}


Chiamate thread-safe a un controllo di Windows Form

Per eseguire una chiamata thread-safe a un controllo di Windows Form

  1. Eseguire una query sulla proprietà InvokeRequired del controllo.

  2. Se InvokeRequired restituisce true, chiamare Invoke con un delegato che esegua la chiamata effettiva al controllo.

  3. Se InvokeRequired restituisce false, chiamare direttamente il controllo.

Nell'esempio di codice riportato di seguito questa logica viene implementata in un metodo di utilità denominato SetText. Il tipo di delegato SetTextDelegate incapsula il metodo SetText. Se la proprietà InvokeRequired del controllo TextBox restituisce true, il metodo SetText crea un'istanza di SetTextDelegate e chiama il metodo Invoke del form. In questo modo la chiamata al metodo SetText viene eseguita sul thread che ha creato il controllo TextBox e la proprietà Text viene impostata direttamente in questo contesto di thread.

		// This event handler creates a thread that calls a 
		// Windows Forms control in a thread-safe way.
		private void setTextSafeBtn_Click(
			object sender, 
			EventArgs e)
		{
			this.demoThread = 
				new Thread(new ThreadStart(this.ThreadProcSafe));

			this.demoThread.Start();
		}

		// This method is executed on the worker thread and makes
		// a thread-safe call on the TextBox control.
		private void ThreadProcSafe()
		{
			this.SetText("This text was set safely.");
		}


		// This method demonstrates a pattern for making thread-safe
		// calls on a Windows Forms control. 
		//
		// If the calling thread is different from the thread that
		// created the TextBox control, this method creates a
		// SetTextCallback and calls itself asynchronously using the
		// Invoke method.
		//
		// If the calling thread is the same as the thread that created
		// the TextBox control, the Text property is set directly. 

		private void SetText(string text)
		{
			// InvokeRequired required compares the thread ID of the
			// calling thread to the thread ID of the creating thread.
			// If these threads are different, it returns true.
			if (this.textBox1.InvokeRequired)
			{	
				SetTextCallback d = new SetTextCallback(SetText);
				this.Invoke(d, new object[] { text });
			}
			else
			{
				this.textBox1.Text = text;
			}
		}


Chiamate thread-safe mediante BackgroundWorker

Il metodo migliore per implementare il multithreading nell'applicazione è utilizzare il componente BackgroundWorker, che utilizza un modello basato sugli eventi per il multithreading. Il thread di lavoro esegue il gestore dell'evento DoWork e il thread che crea i controlli esegue i gestori degli eventi ProgressChanged e RunWorkerCompleted. Prestare attenzione a non chiamare alcun controllo dal gestore dell'evento DoWork.

Poiché nell'esempio di codice riportato di seguito non sono presenti processi da eseguire in modo asincrono, il gestore dell'evento DoWork non è stato implementato. La proprietà Text del controllo TextBox viene impostata direttamente nel gestore dell'evento RunWorkerCompleted.

		// This event handler starts the form's 
		// BackgroundWorker by calling RunWorkerAsync.
		//
		// The Text property of the TextBox control is set
		// when the BackgroundWorker raises the RunWorkerCompleted
		// event.
		private void setTextBackgroundWorkerBtn_Click(
			object sender, 
			EventArgs e)
		{
			this.backgroundWorker1.RunWorkerAsync();
		}
		
		// This event handler sets the Text property of the TextBox
		// control. It is called on the thread that created the 
		// TextBox control, so the call is thread-safe.
		//
		// BackgroundWorker is the preferred way to perform asynchronous
		// operations.

		private void backgroundWorker1_RunWorkerCompleted(
			object sender, 
			RunWorkerCompletedEventArgs e)
		{
			this.textBox1.Text = 
				"This text was set safely by BackgroundWorker.";
		}


Controlli ActiveX in Windows Form

Se si utilizzano controlli ActiveX in un form, è possibile che venga visualizzata l'eccezione InvalidOperationException cross-thread durante l'esecuzione nel debugger. Questo errore indica che il controllo ActiveX non supporta il multithreading. Per ulteriori informazioni sull'utilizzo dei controlli ActiveX con Windows Form, vedere Windows Form e applicazioni non gestite.

Se si utilizza Visual Studio, è possibile impedire che si verifichi questa eccezione disattivando il processo di hosting di Visual Studio.

Procedura: disattivare il processo di hosting
Procedura: disattivare il processo di hosting
Procedura: disabilitare il processo di hosting
Procedura: disabilitare il processo di hosting
Procedura: disabilitare il processo di hosting

Attenzione:

Quando si utilizza qualsiasi tipo di multithreading, è possibile che il codice venga esposto al rischio di errori gravi e complessi. Per ulteriori informazioni, vedere Suggerimenti per l'utilizzo del threading gestito prima di implementare soluzioni che utilizzano il multithreading.

Aggiunte alla community

Mostra: