- 1:概述
- 2:应用场景
- 3:目标
- 4:开始
- 4.1:多线程
- 4.2:委托
- 4.3:小错误
- 5:结果
概述
昨天在写一个串口自动调试工具的时候,需要实现按下一个按钮开始自动调试,按下中止按钮,中止调试。
那么这就需要自动调试的过程是非阻塞式的。
以前遇到需要多任务运行的时候都是使用非阻塞式轮询,这次打算使用多线程进行,结果遇到了一系列问题,所幸最终解决了,并记录如下。
应用场景
任何需要多线程运行任务的情况,比如抽奖程序,按下开始,再次按下停止等等,且在各种游戏中运用广泛。
目标
1、使用一个按钮启动并关闭子线程,并对本线程的TextBox进行控制,每隔1秒钟循环打印1~10;
开始
多线程
首先,写了一个按钮的方法,然后生成子线程,通过MainWindow.mainWindow.sendtomsgbox(string msg)方法将信息打印在公屏textBox上。
public partial class MainWindow : Form
{
//单例
static public MainWindow mainWindow;
private string currentTime;
private bool isAutoCalibStart;
//自动校准子线程
private AutoCalibThread autoCalibThread;
Thread childThread;
public MainWindow()
{
InitializeComponent();
mainWindow = this;
TheadInit();
}
private void MainWindow_Load(object sender, EventArgs e)
{
}
private void TheadInit()
{
autoCalibThread = new AutoCalibThread();
childThread = new Thread(new ThreadStart(autoCalibThread.StartautoCalib));
isAutoCalibStart = false;
}
private void AutoCalibStartButton_Click(object sender, EventArgs e)
{
if (isAutoCalibStart)
{
isAutoCalibStart = false;
childThread.Abort();
}
else
{
isAutoCalibStart = true;
if (childThread != null)
{
childThread.Start();
}
}
}
//发送消息到消息框
public void SendToMsgBox(string msg, bool isNextLine = true)
{
if (isNextLine)
{
this.MsgBox.AppendText(msg + "\r\n");
}
else
{
this.MsgBox.AppendText(msg);
}
}
}
然后出现了第一个问题:“线程间操作无效: 从不是创建控件的线程访问它”。
好吧,仔细一想也是,这种窗口控件的访问本质上不是线程安全的。如果有两个或多个线程操作同一控件的状态,则可能会迫使该控件进入一种不一致的状态。还可能出现其他与线程相关的 bug,包括争用情况和死锁。因此,确保以线程安全方式访问控件非常重要。[参考自互联网]
于是,想到的第一个方法是,把textBox控件独立到一个新的线程,别的线程需要打印信息,需要通过接口给该类传输数据,该类会将数据保存在Buffer中,然后通过定时访问的方式将Buffer中的数据载入到控件里。也就是中间多加了个Buffer缓存,避免了多个线程调用方法直接控制控件。
但是还是打算看看网上的方法,一个是启用线程间操作。
CheckForIllegalCrossThreadCalls=false;
另一个是使用委托。感觉第一个等于没解决,无异于看见BUG不管。遂选择使用较为复杂的委托。
委托
参考网上N个资料和例程,依然不断报错“线程间操作无效: 从不是创建控件的线程访问它”。也就是说委托并没有配置成功,最后不断翻看总算解决了,记录如下:
//以下内容由三个类中的代码块拼接而成
//声明委托,注意委托参数表需要与被委托参数表一致
class DelegateCollection
{
public delegate void MsgSendDategate(string msg, bool isNextLine = true);
}
//声明子线程,并在子线程类中定义委托以方便直接调用
class AutoCalibThread
{
public DelegateCollection.MsgSendDategate msgSendDategate;
public void StartautoCalib()
{
for (int i = 1; i < 10; i++)
{
Thread.Sleep(1000);
if (msgSendDategate != null)
{
msgSendDategate(i.ToString());
}
}
}
}
//重写任务函数(即被委托函数)[注意该函数在拥有被调用控件地类中]
public void SendToMsgBox(string msg, bool isNextLine = true)
{
if (this.MsgBox.InvokeRequired)
{
BeginInvoke(autoCalibThread.msgSendDategate, new object[] { msg, isNextLine });
}
else
{
if (isNextLine)
{
this.MsgBox.AppendText(msg + "\r\n");
}
else
{
this.MsgBox.AppendText(msg);
}
}
}
//进行委托绑定[注意该函数在拥有被调用控件地类中]
private void TheadInit()
{
autoCalibThread = new AutoCalibThread();
autoCalibThread.msgSendDategate = new DelegateCollection.MsgSendDategate(SendToMsgBox);
childThread = new Thread(new ThreadStart(autoCalibThread.StartautoCalib));
}
小错误
使用委托成功地让子线程控制了其他线程地状态,但还是遇到了个小问题,在杀死线程后重新启动出现报错“线程正在运行或被终止;它无法重新启动。”很容易可以想到,使用Abort方法后,线程处在死亡状态,生命周期已经结束。需要重新实例化一个线程,于是将实例化代码从初始化函数移动到启动函数即可。
结果
结果图片
(1) 开启
(2) 重开
Hi Dear, are you in fact visiting this web site daily, if so then you will absolutely get good experience. Fayre Jefferey Adriene
Thank you! Actually, I had someone design it for me. Matilde Agustin Belsky
I cannot thank you enough for the article post. Really looking forward to read more. Keep writing. Karlotta Lucian Manchester