出错代码
// 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
});
}
}
异常信息
中文:
System.InvalidOperationException:“线程间操作无效: 从不是创建控件“textBox1”的线程访问它。”
英文:
System.InvalidOperationException: Cross-thread operation not valid: Control 'textBox1' accessed from a thread other than the thread it was created on.
解决办法
方法1(不推荐):禁用跨线程检查
使用 Control.CheckForIllegalCrossThreadCalls = false;
例如:文章来源:https://www.toymoban.com/news/detail-517788.html
// 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;
});
}
}
方法2:使用委托
使用 Control
基类的 Invoke
或 BeginInvoke
方法,来执行禁止跨线程的代码。
例如:
// 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;
});
});
}
}
或:
// Form1.cs
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Task.Run(() => // 模拟另一线程
{
// 在另一线程内做一些耗时操作
//...
string result = "...";
// 输出结果(不会引发异常)
this.Invoke((MethodInvoker)delegate
{
textBox1.Text = result;
});
});
}
}
使用参数进行传递:
// 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);
});
}
}
不使用 lambda 表达式的写法:
// 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);
}
使用 BeginInvoke
方法的情形,与使用 Invoke
方法类似。二者区别是:Invoke
方法会在主线程(这里指 GUI 线程)内完成工作;BeginInvoke
会在一个新的线程内完成工作(当然,这不会报错)。
方法3:使用 BackgroundWorker 完成整个耗时过程(不仅是改变 GUI)
例如:
// 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(); // 输出结果
}
}
另一个例子,带有进度反馈,且支持取消:
见我的另一篇文章:C# BackgroundWorker 简单示例文章来源地址https://www.toymoban.com/news/detail-517788.html
参考资料
- https://zhuanlan.zhihu.com/p/568602274
到了这里,关于C# 解决 System.InvalidOperationException:“线程间操作无效: 从不是创建控件“...”的线程访问它。”的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!