error code
// Form1.cs
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Task.Run(() => // 模拟另一线程
{
// 在另一线程内做一些耗时操作
//...
string result = "...";
// 输出结果(将引发异常)
textBox1.Text = result; // System.InvalidOperationException
});
}
}
exception information
中文:
System.InvalidOperationException:“线程间操作无效: 从不是创建控件“textBox1”的线程访问它。”
英文:
System.InvalidOperationException: Cross-thread operation not valid: Control 'textBox1' accessed from a thread other than the thread it was created on.
Solution
Method 1 (not recommended): Disable cross-thread checking
useControl.CheckForIllegalCrossThreadCalls = false;
For example:
// Form1.cs
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Control.CheckForIllegalCrossThreadCalls = false; // 禁用跨线程检查
}
private void button1_Click(object sender, EventArgs e)
{
Task.Run(() => // 模拟另一线程
{
// 在另一线程内做一些耗时操作
//...
string result = "...";
// 输出结果(不会引发异常)
textBox1.Text = result;
});
}
}
Method 2: Using delegates
Use the or method Control
of the base class to execute code that prohibits cross-threading.Invoke
BeginInvoke
For example:
// Form1.cs
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Task.Run(() => // 模拟另一线程
{
// 在另一线程内做一些耗时操作
//...
string result = "...";
// 输出结果(不会引发异常)
this.Invoke(() =>
{
textBox1.Text = result;
});
});
}
}
Use parameters to pass:
// Form1.cs
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Task.Run(() => // 模拟另一线程
{
// 在另一线程内做一些耗时操作
//...
string result = "...";
// 输出结果(不会引发异常)
this.Invoke((string text, int length) =>
{
textBox1.Text = $"Text={
text}, Length={
length}";
}, result, result.Length);
});
}
}
Writing without lambda expressions:
// Form1.cs
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Task.Run(() => // 模拟另一线程
{
// 在另一线程内做一些耗时操作
//...
string result = "...";
// 输出结果(不会引发异常)
this.Invoke(new SetTextEvent(SetText), result, result.Length);
});
}
private void SetText(string text, int length)
{
textBox1.Text = $"Text={
text}, Length={
length}";
}
private delegate void SetTextEvent(string text, int length);
}
Using BeginInvoke
the method Invoke
is similar to using the method. The difference between the two is: Invoke
the method will complete the work in the main thread (here refers to the GUI thread); BeginInvoke
it will complete the work in a new thread (of course, this will not report an error).
Method 3: Use BackgroundWorker to complete the whole time-consuming process (not only changing the GUI)
For example:
// Form1.cs
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
using (BackgroundWorker backgroundWorker = new BackgroundWorker())
{
backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted);
backgroundWorker.RunWorkerAsync(); // 可以传入参数
}
// 后续过程(不会被 BackgroundWorker 阻塞,即不会等待其完毕再执行)
// ...
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
//e.Argument // 可以取得传入的参数
// 模拟耗时操作
//...
string result = "...";
// 传递结果
e.Result = result;
}
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
textBox1.Text = e.Result.ToString(); // 输出结果
}
}
Another example with progress feedback and support for cancellation:
see my other article: C# BackgroundWorker simple example