C#使用委托跨线程调用控件方法

文章目录[x]
  1. 1:概述
  2. 2:应用场景
  3. 3:目标
  4. 4:开始
  5. 4.1:多线程
  6. 4.2:委托
  7. 4.3:小错误
  8. 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) 重开

点赞
  1. erotik说道:

    Hi Dear, are you in fact visiting this web site daily, if so then you will absolutely get good experience. Fayre Jefferey Adriene

  2. porno说道:

    Thank you! Actually, I had someone design it for me. Matilde Agustin Belsky

  3. porno说道:

    I cannot thank you enough for the article post. Really looking forward to read more. Keep writing. Karlotta Lucian Manchester

发表评论

昵称和uid可以选填一个,邮箱必填(用于评论被回复时的邮件提醒,不会被公开)
tips:输入uid可以获取你的B站昵称和头像