概述
昨天在写一个串口自动调试工具的时候,需要实现按下一个按钮开始自动调试,按下中止按钮,中止调试。
那么这就需要自动调试的过程是非阻塞式的。
以前遇到需要多任务运行的时候都是使用非阻塞式轮询,这次打算使用多线程进行,结果遇到了一系列问题,所幸最终解决了,并记录如下。
应用场景
任何需要多线程运行任务的情况,比如抽奖程序,按下开始,再次按下停止等等,且在各种游戏中运用广泛。
目标
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方法后,线程处在死亡状态,生命周期已经结束。需要重新实例化一个线程,于是将实例化代码从初始化函数移动到启动函数即可。
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