最近在做一个简单点的winform项目的时候,因为要在子线程中控制主线程UI显示日志,在停止线程的时候竟然出现所有线程卡死的情况,这下疯了,难道我做游戏久了,竟然连简单的winform跨线程都不会了么?请看代码,大家看看能否找出原因
using System;
using System.Threading;
using System.Windows.Forms;
namespace GamePlugInDemo
{
public partial class Form2 : Form
{
///
/// 输出事件
///
Action outputAction;
///
/// 工作线程是否工作标记位
///
bool bRun=false;
///
/// 工作线程
///
Thread workThread;
public Form2()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
this.button1.Text = "开始";
outputAction += Output;
base.OnLoad(e);
}
private void Output(string info)
{
if (this.InvokeRequired)//这个请求是跨线程的话,那么就使用invoke调用控件
{
this.Invoke(outputAction, info);
}
else
{
//不是跨线程操作,直接使用控件
this.label1.Text = info;
}
}
private void button1_Click(object sender, EventArgs e)
{
if (!bRun)
{//没运行时点击按钮-开始子线程工作
bRun = true;
workThread = new Thread(Work);
workThread.Start();
this.button1.Text = "停止";
}
else
{//子线程开始了后点击按钮-停止子线程工作
bRun = false;
workThread.Join();//等待子线程停止
this.button1.Text = "开始";
Console.WriteLine("停止子线程按钮完毕");
}
}
private void Work()
{
while (bRun)
{
Output(DateTime.Now.ToString("yyyy-MM-dd-HH:mm:ss"));
Thread.Sleep(100);
}
Output("结束时间为:"+DateTime.Now.ToString("YYYY-MM-dd-HH:mm:ss"));
Console.WriteLine("子线程结束");
}
}
}
以上代码就是一个简单的按钮和一个label显示当前时间,每100毫秒刷新一次UI的时间显示,点击按钮会开始和停止.
开始阶段没有问题,但是在点击按钮停止线程的时候需要确定工作线程已经结束后才继续响应UI(正式项目其实是为了暂停UI响应,保证工作线程的计算结果正确,工作线程正常退出!);此时就出现了整个程序卡死的情况,我百撕不得骑姐,检查了下代码,发现跨线程调控件没有问题,发现工作线程Output也运行了,可最后的Console却一次没有运行,打断点发现工作线程却一直没有结束.所以按钮哪里一直在等待workThread.Join();卡主,然后工作线程不能结束,主线程一直等待!于是就出现了卡死!貌似永远不能结束了!
不知道同学们有没有发现上面的原因在哪里呢?
经过几个小时的断点检查,竟然发现是跨线程输出这里卡住了??怎么回事?用invoke调用主线程UI没错啊,大家也都这样用的?然后我仔细看了下invoke的解释:在拥有控件的基础窗口句柄的线程上,用指定的参数列表执行指定委托。
我去?原来是换到主线程调用而已,可主线程在干啥呢?主线程不是在等待子线程结束吗?所以主线程一直没有运行这个invoke内容,而没有给子线程返回,所以子线程在等待主线程调用invoke结束的返回,好吧,主线程等子线程才继续工作,子线程等主线程工作完了才正式结束,于是一个双线程死锁就出现了!
好吧好吧,看起来只要让某一个线程别等待另外一个线程就可以解开这个锁了.但是业务需要必须等待子线程正常结束,看来只能让子线程别等主线程的返回就可以了!所以我就用了BeginInvoke函数来调用子线程了!这个函数不用等待返回会继续子线程的工作!于是锁就解开了!以前写winform总是习惯用invoke,毕竟字打的少点,效果也是杠杠的.经过这次,发现同步调用对线程的影响还是很大的!SO:以后的跨线程调用就用 this.BeginInvoke(outputAction,info)函数!
以上的代码改掉其中一个就可以了!代码如下:
private void Output(string info)
{
if (this.InvokeRequired)//这个请求是跨线程的话,那么就使用invoke调用控件
{
//this.Invoke(outputAction, info);//警告,不要用同步调用,不然会让你哭
this.BeginInvoke(outputAction, info);
}
else
{
//不是跨线程操作,直接使用控件
this.label1.Text = info;
}
}
OK!解决了一个疑难BUG.哈哈!心里舒服了,去找公司新来的主播聊骚去!