C# winform 窗体控件跨线程访问的实现

 更新时间:2023年12月14日 09:47:24   作者:Big Clever.  
在做winform开发时,如果在子线程中去设置主线程中UI控件的属性,会出现“跨线程调用异常”,本文就来介绍一下C# winform 窗体控件跨线程访问的实现,感兴趣的可以了解一下

在写计算机网络课设的时候,遇到一个需求:建立的服务器需要一直监听来自某个端口的连接请求。一开始看到我想这不是很简单吗,正好刚刚学习了线程有关的知识,直接开个新线程挂在后台执行不就行了,抱着这样简单的想法,一堆错误接踵而至。

首先出现的问题就是窗体控件的跨线程访问,会出现这样的一个报错信息:System.InvalidOperationException:“线程间操作无效: 从不是创建控件“textBox1”的线程访问它。”

去找了找解决方案,其中最简单的是这样的一行代码:

Control.CheckForIllegalCrossThreadCalls = false;

这行代码只需要加到需要进行控制的控件那里,就会让程序忽略线程的危险操作。

这样确实就解决了问题,但是这是一个全局的静态变量,如果在其他地方不小心修改了这一变量,每个控件就又会崩掉。所以,正统的解决方法应该是使用委托。

什么是委托?C#的委托类似于C++的函数指针,是一个可以批量执行函数的容器。我们知道这个问题是发生在跨线程访问中,那么我们如果让函数调用发生在创建这个控件的线程中,那么就不是跨线程访问,也就不存在这个问题了,因此我们使用this.invoke(delegrate)来使用委托。

来解释这种解决方法,我们设想一个简单的情景:我们需要在一个文本框中实时的更新当前时间,我们希望同时还可以在窗体中进行别的操作。

第一时间想到:

private void Form1_Load(object sender, EventArgs e)
{
    while(true) 
    { 
        string time = DateTime.Now.ToString();
        textBox1.Text = time;
        Thread.Sleep(1000);
    }
}

这个显然是错误的,甚至连窗口都跳不出来,更别提还可以在窗口中执行其他操作。因为这个while(true)循环堵死了这个窗体的加载,根本加载不出来,会一直停留在加载这个环节。

然后就应该想到使用新开线程来解决这个问题了:

private void Form1_Load(object sender, EventArgs e)
{
    // Control.CheckForIllegalCrossThreadCalls = false; 
    // 如果加上上面这行代码,就可以运行了,但是只是忽略了线程安全性
    Thread timeShower = new Thread(ShowTime);
    timeShower.IsBackground = true;
    timeShower.Start();

}
private void ShowTime()
{
    while(true)
    {
        string time = DateTime.Now.ToString();
        textBox1.Text = time;      
    }
}

很可惜,线程的第一次使用报错了!跨线程访问了textbox控件,这不安全。

接着,我们想使用委托的相关知识来解决这个问题:

private void Form1_Load(object sender, EventArgs e)
{
    // 这个线程传递的是我们使用委托的那个函数
    Thread timeShower = new Thread(Textbox1ShowTime);
    timeShower.IsBackground = true;
    timeShower.Start();

}
private delegate void TextBox1Delegrate(); // 声明了一个无参无返回的委托
private void Textbox1ShowTime()
{
    // 实例化委托,传递实际需要调用的ShowTime函数
    TextBox1Delegrate newDelegrate = new TextBox1Delegrate(ShowTime);
    while (true) 
    {
        this.Invoke(newDelegrate); // 这里的this代指Form1
        Thread.Sleep(1000);
    }
}
private void ShowTime()
{
    string time = DateTime.Now.ToString();
    textBox1.Text = time;
}

好的,我们已经基本上完成了我们设想的情景。但是,我们能否一步到位,将while循环和Thread.Sleep(1000) 放到ShowTime函数中,然后直接使用委托启动ShowTime,就可以直接得到实时刷新的时间呢?

如果你和我一样聪明,估计会这么想:

private void Form1_Load(object sender, EventArgs e)
{
    Thread timeShower = new Thread(Textbox1ShowTime);
    timeShower.IsBackground = true;
    timeShower.Start();

}
private delegate void TextBox1Delegrate();
private void Textbox1ShowTime()
{
    TextBox1Delegrate newDelegrate = new TextBox1Delegrate(ShowTime);
    this.Invoke(newDelegrate);
}
private void ShowTime()
{
    while (true)
    {
        string time = DateTime.Now.ToString();
        textBox1.Text = time;
        Thread.Sleep(1000);
    }
}

好的,恭喜你,获得了一个无响应卡死的窗口!这是为什么呢?首先我们要知道this.Invoke(function)的原理是将这个function发送到创建this的这个线程,在这里也就是主线程中执行,因此来规避跨线程问题。但是这里,我们传了一个永真循环进去,其实和我们一开始想到的那个代码没有本质上的区别,能显现出窗口只是因为我们使用了委托(那还是进步了,恭喜我们)。

那么应该怎么样才能实现一步到位的解决这个实时显示时间的问题呢?

好,其实我也实现不了一步到位,如果有大神会的,一定要在评论区教教我QAQ

但是可以实现一个两重外包,然后实现直接调用委托实时显示时间。究其根本,只要不把永真的while循环放到主线程中执行,就可以了,所以这两重外包就是把while单独分离了(其实上面那种实现方法也是同样的思路,分离了永真while这个讨厌的东西)

private void Form1_Load(object sender, EventArgs e)
{
    Thread timeShower = new Thread(Textbox1ShowTime);
    timeShower.IsBackground = true;
    timeShower.Start();

}
private delegate void TextBox1Delegrate();
private void Textbox1ShowTime()
{
    TextBox1Delegrate newDelegrate = new TextBox1Delegrate(ShowTime);
    newDelegrate.Invoke();
}
private void ShowTime()
{
    while (true)
    {
        TextBox1Delegrate newDelegrate = new TextBox1Delegrate(ShowTime1);
        this.Invoke(newDelegrate);
        Thread.Sleep(1000);
    }
}
private void ShowTime1()
{
    string time = DateTime.Now.ToString();
    textBox1.Text = time;
}

同样的,顺着分离这个思路走下去其实还可以进一步精简我们的代码。

这边要提到一个属性 textBox1.InvokeRequired。这个属性是bool类型,他表示获取一个值,该值指示调用方在对控件进行方法调用时是否因为调用的方法于不在创建控件的线程中,是否必须调用 Invoke 方法。

对于有许多控件和线程需要进行操作的情况而言,这个属性可以非常方便的确定是否需要使用Invoke()方法进行委托的调用

对于我们这个从子线程Textbox1ShowTime()进去的ShowTime而言,这个属性永远是真的;而对于我们使用this.Invoke(newDelegrate)使用的ShowTime而言,它相当于是在主线程中执行这个函数,从而实现了状态的分离。因此我们可以通过if(textBox1.InvokeRequired)实现将ShowTime1放到ShowTime中实现

private void Form1_Load(object sender, EventArgs e)
{
    Thread timeShower = new Thread(Textbox1ShowTime);
    timeShower.IsBackground = true;
    timeShower.Start();

}
private delegate void TextBox1Delegrate();
private void Textbox1ShowTime()
{
    TextBox1Delegrate newDelegrate = new TextBox1Delegrate(ShowTime);
    newDelegrate.Invoke();
}
private void ShowTime()
{
    if (textBox1.InvokeRequired) // 从new Thread(Textbox1ShowTime)调用的委托一定会进这里
    {
        while (true)
        {
            TextBox1Delegrate newDelegrate = new TextBox1Delegrate(ShowTime);
            this.Invoke(newDelegrate); // 这个相当于是在主线程调用委托
            Thread.Sleep(1000); // 注意这个sleep不能放到else中,不然会无限的休眠
        }
    }
    else // 从主线程调用的委托一定会进到这里
    {
        string time = DateTime.Now.ToString();
        textBox1.Text = time;
    }

}

OK,针对这个跨线程访问的例子暂时我只能想这么多了,有错误的话大佬们不灵赐教啊QAQ

到此这篇关于C# winform 窗体控件跨线程访问的实现的文章就介绍到这了,更多相关C# winform跨线程访问内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:

相关文章

  • WPF利用TextBlock实现查找结果高亮显示效果

    WPF利用TextBlock实现查找结果高亮显示效果

    在应用开发过程中,经常遇到这样的需求:通过关键字查找数据,把带有关键字的数据显示出来,同时在结果中高亮显示关键字,所以本文就来和大家介绍一下如何利用TextBlock实现查找结果高亮显示效果吧
    2023-08-08
  • 浅谈C#中的string驻留池

    浅谈C#中的string驻留池

    这篇文章主要介绍了C#中的string驻留池的的相关资料,文中示例代码非常细致,供大家参考和学习,感兴趣的朋友可以了解下
    2020-06-06
  • 深入理解C#之枚举

    深入理解C#之枚举

    这篇文章主要介绍了C#中可枚举类型,IEnumerable和IEnumerator接口及其泛型实现和迭代器,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-07-07
  • C#创建dll类库的图文步骤

    C#创建dll类库的图文步骤

    类库让我们的代码可复用,我们只需要在类库中声明变量一次,就能在接下来的过程中无数次地使用,而无需在每次使用前都要声明它。这样一来,就节省了我们的内存空间,需要的朋友可以参考下
    2017-01-01
  • 图形学之Unity渲染管线流程分析

    图形学之Unity渲染管线流程分析

    这篇文章主要介绍了图形学之Unity渲染管线流程的相关资料,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-01-01
  • Unity AssetPostprocessor模型函数Model实用案例深入解析

    Unity AssetPostprocessor模型函数Model实用案例深入解析

    这篇文章主要为大家介绍了Unity AssetPostprocessor模型Model函数实用案例深入解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-05-05
  • C#中使用Microsoft Unity记录日志

    C#中使用Microsoft Unity记录日志

    这篇文章介绍了C#中使用Microsoft Unity记录日志的方法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-08-08
  • C#如何打开选择文件对话框和选择目录对话框

    C#如何打开选择文件对话框和选择目录对话框

    这篇文章主要介绍了C#如何打开选择文件对话框和选择目录对话框问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-07-07
  • C#实现的几种委托方式介绍

    C#实现的几种委托方式介绍

    这篇文章主要是介绍C#实现的几种委托方式,需要的朋友可以参考下
    2013-03-03
  • C# 启用事务提交多条带参数的SQL语句实例代码

    C# 启用事务提交多条带参数的SQL语句实例代码

    这篇文章主要介绍了C# 启用事务提交多条带参数的SQL语句实例代码,需要的朋友可以参考下
    2018-02-02

最新评论