在C#的多线程访问中,在线程间的相互访问时因为线程安全问题有访问限制,在创建一般线程时,对于界面元素访问时这样的问题比较常见。
比如,创建一个form1,上面放置一个textbox控件,创建一个线程去访问textbox,界面如下:
按钮buuton1的代码:
private void button1_Click(object sender, EventArgs e)
{
var thread1 = new System.Threading.Thread(Func1);
thread1.Start();
}
就是简单地创建一个线程,线程里面运行的func1代码:
private void Func1()
{
for(int i = 0; i < 5; i++)
{
textBox1.Text = textBox1.Text+ $"线程{Thread.CurrentThread.ManagedThreadId}执行{i}."+Environment.NewLine;
}
}
运行,点击button1按钮系统会报错:
意思是说:线程间操作无效:从不是创建控件“textbox1”的线程访问它,这是不允许的。
线程存在安全保护机制,并不能随意访问,因为这样存在冲突的可能。
解决这个问题,最直接的方法是在界面初始化后去掉控件的跨线程非法访问属性,即将Control.CheckForIllegalCrossThreadCalls属性设置为false即可。
public Form1()
{
InitializeComponent();
Control.CheckForIllegalCrossThreadCalls = false;
}
这样结果就出来了。
一般情况下,还是不要设置Control.CheckForIllegalCrossThreadCalls的属性,那么怎样可以达到修改textbox1的值呢?
可以通过委托来解决。
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
var thread1 = new System.Threading.Thread(Func1);
thread1.Start();
}
public void SetText(string SText)
{
textBox1.Text = textBox1.Text + SText + Environment.NewLine;
}
private void Func1()
{
string Str = "";
for (int i = 0; i < 5; i++)
{
Str= $"线程{Thread.CurrentThread.ManagedThreadId}执行{i}."+Environment.NewLine;
if (textBox1.InvokeRequired)
{
Action SetText111 = delegate { SetText(Str); };
textBox1.Invoke(SetText111);
}
else
{
textBox1.Text = Str;
}
}
}
}
这样也可以达到目的,或者直接写更简单:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
var thread1 = new System.Threading.Thread(Func2);
thread1.Start();
}
private void Func2()
{
textBox1.Invoke(() =>
{
for (int i = 0; i < 5; i++)
{
textBox1.Text = textBox1.Text+ $"线程{Thread.CurrentThread.ManagedThreadId}执行{i}." + Environment.NewLine;
}
});
}
}
效果也是一样的。
在C#中,需要注意Invoke和begininvoke的区别。
control.invoke(参数delegate):在拥有此控件的基础窗口句柄的线程上执行指定的委托,注意是同步。
control.begininvoke(参数delegate):在创建控件的基础句柄所在线程上异步执行指定委托,注意这里执行的是异步。
在跨线程请求时,常检验textBox1.InvokeRequired属性,即是否跨线程请求。