• 多线程之间的非安全调用处理之 从UI线程之外修改UI信息


    曾经刚接触时多线程,认为甚是简单,短短的几行代码就可以借助封装好的方式实现自己的多线程。 这几天学习在做C/S小项目时间碰见一个调试异常,才让我真正沉下去认识线程,幸亏还有当年所学操作系统的知识,线程的调度知识,认识才更深一步。在这里我将以前的调试异常截图,这篇文章就从此处入手,解释我对线程的肤浅认识。提示信息如下图所示:

    搜狗截图_2012-07-11_16-28-45

    仔细看代码,发现项目中的列表框(listBox)是在UI主线程中的,而引起异常操作的地方发生在逻辑处理的又一个线程之中,在非UI线程调用UI线程,修改其属性造成了这样的异常:“线程间的操作无效”,我知道只要在UI线程的构造函数关闭线程调用异常检测就可以避免这个提示,程序继续正常走。可以这样实现:

    1: System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = false;

    回头想起这段代码,总觉得这样子做并不是最好的办法。我回去查了资料,发现原先的.Net版本是没有引入线程调用异常的检测的,为了安全性微软在线程的调用时引入了安全检测。一种很糟糕的情况:如果有两个或多个线程操作某一控件的状态,则可能会迫使该控件进入一种不一致的状态。还可能出现其他与线程相关的 bug,包括争用情况和死锁。确保以线程安全方式访问控件非常重要。

             既然微软引入了安全线程的监测,那我们就应该尽量构建自己的安全代码,而不是在此处为了图方便设置去除安全机制,其实解决此类问题并不是很难,关键是真正的理解多线程的操作。多线程的使用看似只有那三四行代码,但是要想真正的明白调度的过程,使用的技巧却不是三言两语就可以明白的。这里我从自己的理解角度出发,联系自己看过的书籍对多线程的使用做出自己的解释。

    1)线程:

           线程是程序中一个单一的顺序控制流程。在单个程序中同时运行多个线程完成不同的工作,称为多线程。多线程处理一个常见的例子就是用户界面。利用线程,用户可按下一个按钮,然后程序会立即作出响应,而不是让用户等待程序完成了当前任务以后才开始响应。从程序设计语言的角度看,多线程操作最有价值的特性之一就是程序员不必关心到底使用了多少个处理器。程序在运行中就会虚拟有N个线程同时进行,相互中间没有影响,相当于N个处理器一样的工作效率,程序的处理速度自然加快。这时间就不得不注意一个问题-----没错,就是“资源共享”,面对资源共享的问题,我们知道两个线程是不允许同时同步操作一个资源的,例如:一台打印机在某时刻只允许一个线程在操作。面对这样的问题,多线程采用Lock方法解决。当完成此任务的时候解锁释放资源。(此时有没有想到大学读的操作系统的资源的调度哦)

              再次必须郑重声明一点:多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率。线程是在同一时间需要完成多项任务的时候实现的。

    2)线程与进程的区别(进程VS线程

             看到这个小标题,每一个学习过计算机的同志都会在心里想出来点什么,但具体是什么又很难一时间全部是清楚,此处就作补充的吧想不明白的说一下:

    属于一个单一的应用程序的所有的线程逻辑上被包含在一种进程之中(线程是进程的子单位),进程是指一个应用程序所运行的操作系统单元。

    简单来讲:进程是表示资源分配的基本单位,又是调度运行的基本单位;线程是进程中执行运算的最小单位,亦即执行处理机调度的基本单位。资源分配给进程,同一进程的所有线程共享该进程的所有资源。处理机分给线程,即真正在处理机上运行的是线程。

    3)共享函数---线程不安全的根源

    共享数据可以是:

    • 1>函数把返回结果放到一个公共的位置
    • 2>由调用者传入的线程间共享的指针变量或者引用变量
    • 3>函数内部本来就会使用的共享静态变量

    4)线程的可重入与安全

             可重入:当被多个线程调用的时候,不会引用任何共享数据。我们可以理解为任何线程不安全的因素都是来源于“资源共享”,如果不适用任何共享是函数,线程是绝对安全的,但是多线程的优势在于共享资源。因此解决问题并非是拒绝共享,而是正确调度,并发操作!这就是从不安全的线程操作到安全的线程操作。

             线程不安全怎么能够改写成线程安全:

    • 1>.仍旧使用共享数据,但是在使用共享数据的时候做同步操作。对于函数过程中使用的共享数据,可以进行简单的PV操作,对于返回结果可以在PV操作中把共享数据拷贝到非共享的位置,以便及时释放共享变量。
    • 2>.杜绝使用共享变量。也就是说把函数改成了可重入的函数。但是,彻底杜绝共享变量有的时候不容易做到。对于一个不接受引用和指针的函数,我们可以把它做到绝对的可重入,但是,对于一个接受指针或者引用的函数来说,对不起,我们不能确保他肯定是可重入的。

            这两种方式那个比较好呢?通常来说,多线程是为了在同一时间内能够处理更多的同样类型的事情,但是线程不安全却阻碍了我们达到我们的目的。所以,我们有的时候不得不想方设法的把线程不安全的函数改写成线程安全的。除了第二种所说的劣势之外,可重入,杜绝使用共享函数也是不太理想的,但是使用可重入方式的天然优势是充分利用了处理机的优势,不存在资源的等待释放,PV操作以及软件上的瓶颈,等,各有优势视情况取舍。

    话又说回来了,每当我们拿到一个线程不安全函数是一件郁闷的事情。但是有的时候你必须要使用它,那怎么办呢?这个时候就要从上面提到的三个可能的共享数据来入手了:

                1>如果调用函数的返回结果是共享的,进行简单的PV操作,对于返回结果可以在PV操作中把共享数据拷贝到非共享的位置,以便及时释放共享变量。

                2>尝试在调用函数的时候不使用引用或者指针,就不会有共享函数

                3>找到这个函数的共享变量,对其做PV操作,或者修改代码,不适用这个静态变量

    回到原始的问题,该怎么解决呢?第一就是设置不异常检测:CheckForIllegalCrossThreadCalls = false,第二种办法就是对window窗体多程程的安全调用

    对 Windows 窗体控件进行线程安全调用步骤:
      1. 查询控件的 InvokeRequired 属性。

      2. 如果 InvokeRequired 返回 true,则使用实际调用控件的委托来调用 Invoke。

      3. 如果 InvokeRequired 返回 false,则直接调用控件。  例如:在UI线程之外修改UI内部的属性

    if (this.textBox1.InvokeRequired)  //检测是否有线程占用 
              {    
                  SetTextCallback d = new SetTextCallback(SetText); 
                  this.Invoke(d, new object[] { text }); //使用委托的方式 
              } 
              else 
              { 
                  this.textBox1.Text = text;  //直接使用 
              }

    照此样子就会解决这个问题!

     
     
    标签: 多线程
  • 相关阅读:
    MySQL数据库可以用任意ip连接访问的方法
    Docker packaging guide for Python
    Alpine makes Python Docker builds 50× slower, and images 2× larger
    hasura graphql-engine v1.0 发布了
    edgedb 1.0 Alpha 2 了
    aceql-http docker 试用
    deno学习五 typescript + vscode 集成
    makeself 试用
    poolifier nodejs 线程池工具
    Java Command Line Parsers Comparison
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2586988.html
Copyright © 2020-2023  润新知