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跨线程访问内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C#中String.LastIndexOf方法小结

    C#中String.LastIndexOf方法小结

    String.LastIndexOf()是C#中string类的一个方法,它用于在字符串中查找指定子字符串(或字符)最后一次出现的位置,并返回其索引,本文主要介绍了C#中String.LastIndexOf方法小结,感兴趣的可以了解一下
    2024-01-01
  • C#实现格式化SQL语句的示例代码

    C#实现格式化SQL语句的示例代码

    这篇文章主要为大家详细介绍了C#如何实现格式化SQL语句的功能,文中的示例代码简洁易懂,具有一定的借鉴价值,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-08-08
  • 详解如何获取C#类中发生数据变化的属性信息

    详解如何获取C#类中发生数据变化的属性信息

    这篇文章主要介绍了详解如何获取C#类中发生数据变化的属性信息,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-05-05
  • Unity Undo实现原理和使用方法详解

    Unity Undo实现原理和使用方法详解

    本文将详细介绍Unity Undo实现原理和使用方法,并提供多个使用例子,帮助开发者更好地理解和应用该功能,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-07-07
  • C# 中将数值型数据转换为字节数组的方法

    C# 中将数值型数据转换为字节数组的方法

    C# 中将数值型数据转换为字节数组的方法,需要的朋友可以参考一下
    2013-05-05
  • 详解如何选择使用ArrayList、HashTable、List、Dictionary数组

    详解如何选择使用ArrayList、HashTable、List、Dictionary数组

    本文详细介绍了ArrayList、HashTable、List、Dictionary的用法,以及什么情况选用该数组,以便提高开发效率。希望对大家有所帮助
    2016-11-11
  • C#实现根据数字序号输出星期几的简单实例

    C#实现根据数字序号输出星期几的简单实例

    这篇文章主要介绍了C#实现根据数字序号输出星期几的简单实例,代码简洁实用,也有助于初学者更好的理解C#的switch和if语句的流程控制,需要的朋友可以参考下
    2014-07-07
  • C#中sizeof的用法实例分析

    C#中sizeof的用法实例分析

    这篇文章主要介绍了C#中sizeof的用法,包括了常见的用法及注释事项,需要的朋友可以参考下
    2014-09-09
  • DataGridView设置单元格的提示内容ToolTip

    DataGridView设置单元格的提示内容ToolTip

    这篇文章介绍了DataGridView设置单元格提示内容ToolTip的方法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-02-02
  • C#多线程之线程池ThreadPool用法

    C#多线程之线程池ThreadPool用法

    这篇文章介绍了C#多线程之线程池ThreadPool的用法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-03-03

最新评论