1. 引言
最近,正在进行一个基于C# WinForm的上位机软件的开发与调试工作。项目中遇到一个问题:对画框在在执行某种操作的时候,主界面的显示数据不再更新,直到业务执行完毕。借助多线程,可以很好的解决这个问题,实现并发执行。
使用多线程,可以实现代码的隔离和并发执行,提高应用程序的可靠性和执行效率。
2. 多线程编程
(1)通过委托传递线程函数
线程函数通过委托传递,委托有两种:不带参数的ThreadStart和带参数的ParameterizedThreadStart。
// 不能传递参数,也不能有返回值
ThreadStart threadStart =new ThreadStart(this.DoSomething);
Thread t1 = new Thread(threadStart);
t1.Start();
// 可以传递一个参数,但不能有返回值
ParameterizedThreadStart paramThreadStart = new ParameterizedThreadStart(this.DoSomething);
Thread t2 = new Thread(paramThreadStart);
t2.Start("Parameter");
// 传递匿名方法,可以实现多个参数和返回值的效果
int param = 100;
int ret = 0;
ThreadStart noNameStart =new ThreadStart(delegate()
{
ret = param * 10;
});
Thread t3 = new Thread(threadStart);
t3.Start();
(2)使用专门的线程类传递线程函数
线程函数通过自定义线程类传递,可以实现传递任意数目参数和返回值。
(3)使用委托开启多线程
上面两种方式虽然利用不同的形式传递线程函数,但开启线程都采用线程对象的Start方法开启线程。这里介绍一种通过委托开启线程的方法。
(未完待续)
(4)使用线程池
过多线程的创建和销毁会较多的内存消耗,这时候可以考虑使用线程池。线程池维护一个请求队列,线程池的代码从队列提取任务,然后委派给线程池的一个线程执行。线程执行完并不会立即销毁,这样既可以后台执行任务,又可以减少线程创建和销毁的开销 。
使用方法如下:
// 带参数
WaitCallback wc1 = new WaitCallback(this.DoSomething);
ThreadPool.QueueUserWorkItem(wc1);
// 不带参数
WaitCallback wc2 = new WaitCallback(this.DoSomething);
ThreadPool.QueueUserWorkItem(wc2, "Parameter");
3. 避免跨线程更新UI
跨线程更新UI是写多线程程序尤其是通信类程序经常遇到的问题,这里面主要的问题是冲突,比如数据线程想要更新UI的时候,用户线程同时也在更新UI。解决这个问题主要有两种方法:
(1)关闭跨线程监测
// 关闭跨线程监测
Control.CheckForIllegalCrossThreadCalls = false;
这种方式有一定的风险,容易使程序崩溃。
(2)使用Invoke
private void UpdateText(string strParam)
{
// 当前线程是创建线程(界面线程)
if (myControl.InvokeRequired)
{
// 利用委托更新
myControl.Invoke(new InvokeDelegate(UpdateText), new object[]{strParam});
}
else
{
// 直接更新
myControl.Text = strParam;
}
}
判断当前线程是不是控件的创建线程,如果是就直接更新,否则就建立一个Invoke对象,设置好代理和参数,然后再调用Invoke。
(3)使用自定义委托
ThreadPool.QueueUserWorkItem(new WaitCallback(this.DoSomething), new object());
private void DoSomething(object o)
{
// MyInvokeDelegate为自定义委托
this.Invoke(new MyInvokeDelegate(UpdateText), o.ToString());
}
(4)使用System.Action
ThreadPool.QueueUserWorkItem(new WaitCallback(this.DoSomething), new object());
private void DoSomething(object o)
{
// 传递一个参数的命名方法
this.Invoke(new Action<string>(UpdateText, o.ToString()));
// 匿名方法
this.Invoke(new Action(delegate(){this.textBox.Text = o.ToString();}));
}
System.Action有多个重载,可以无参数,最多可以有4个参数,还可以采用匿名方法,但是没有返回值。
(5)使用System.Func
ThreadPool.QueueUserWorkItem(new WaitCallback(this.DoSomething), new object());
private void DoSomething(object o)
{
// 在Invoke调用主窗体操作后,还可以得到一个返回值
object result = this.Invoke(new Func<string, int>(this.NameToCode), o.ToString());
}
System.Func与System.Action类似,同样有很多泛型重载,最多可以有16个参数,且必须有返回值。