什么是线程

说话一:进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.

线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.

线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.

一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行

说法二:进程和线程都是由操作系统所体会的程序运行的基本单元,系统利用该基本单元实现系统对应用的并发性。进程和线程的区别在于:

简而言之,一个程序至少有一个进程,一个进程至少有一个线程.

线程的划分尺度小于进程,使得多线程程序的并发性高。

另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。

线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。

•线程(thread),有时也被称为轻量级进程(lightweight process , LWP)
–线程是CPU使用的基本单元
–线程由如下部分组成:
       •线程ID
       •PC指针
       •一组寄存器
       •调用栈
 
单线程进程和多线程进程
 
–同一进程内的所有线程共享代码段、数据段和其它操作系统资源 (如文件、信号等)。
 
为什么使用线程

我们一般编写的程序代码总是从 main 函数(控制台),Sub New()(类的构造函数),Load(窗体加载)开始执行的,从上往下,执行每一个调用,有明确的先后顺序,一个sub或者function完成之后,才进行下一个sub或者function。的确,这样程序的逻辑性很强,每个过程排队,依次来。
 但是,这样必然会在一定程度上降低应用程序的运行效率,如果某个过程代码很长,所需要的时间长,那么程序在执行这个过程的时候会出现“假死”,停止响应用户操作,知道过程全部执行完毕。
 关于“假死”:我们编写的Windows应用程序有一个UI线程,用于接收和响应用户界面的操作。而我们编写的代码一般都是基于这个线程的,位于单一线程中的代码也是从上往下依次进行,所以当UI线程中某一过程花费的时间很长时,界面不再响应,因为它很忙,这时就出现了长时间的停顿,也就是“假死”,而用户会认为这很卡。
 因此,如果我们在UI线程的基础上另开一个线程,让代码分支执行的话,就不会卡了。但同时,我们又不得不面临这样一个问题:万一线程执行的过程,和UI执行的过程有冲突怎么办?当你在非UI线程中调用UI线程中的某一个控件,设置它的某某属性,这时你会收到这样一条错误:
 →线程间操作无效: 从不是创建控件“xxx”的线程访问它。
怎么办啊? 别急,后文会有解释。

首先,我们来体验一下使用线程带来的好处和问题。

1.创建Windows窗体应用程序随便弄个名。
2.在窗体上放两个控件,Label1个Button1,如图,其他属性默认:

我们想实现这样一个功能:在Label1上面动态显示数字,从0~9000,我们希望看到数字的变化。

Public Class Form1

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click

Call testA()

End Sub

Private Sub testA()

For i = 0 To 9000

Label1.Text = i.ToString

Next

End Sub

End Class

意思就是点击按钮,进入test过程中,通过For循环,依次显示数字,真是这样吗?运行试试...
不出我意料的话,最后直接显示的是9000,中间还卡了一下。后面的0都不在了。。

那么,我们要显示动态变化又怎么办呢?

我们把上面的代码修改一下,使用线程。

Imports System.Threading '导入线程命名空间
Public Class Form1
    Dim t As Thread  '定义一个全局的线程变量
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        t = New Thread(AddressOf testA)  '创建线程,使它指向 TestA 过程,注意该过程不能带有参数
        t.Start() '启动线程
    End Sub
    Private Sub testA()
        For i = 0 To 9000
            Label1.Text = i.ToString
        Next
        t.Abort() '运行完后终止线程
    End Sub
End Class

再次运行,点击确定,出错啦?什么错?如图:

由于是从一个新的线程调用UI线程中窗体控件,所以这个做法很危险,你直接被拒绝了。
有一个解决办法,就是让编译器不进行跨线程检查。

就是在 Click 代码第一行加一句:
CheckForIllegalCrossThreadCalls = False

CheckForIllegalCrossThreadCalls 方法获取或设置一个值,该值指示是否捕获对错误线程的调用,它在调试期间访问的是空间的句柄,如果该值设置为 False,则表示禁止软件对于不符合原则的跨线程运行的程序进行检查。更为简单的理解就是------忽略程序跨越线程运行导致的错误。

如下代码:

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click     
        CheckForIllegalCrossThreadCalls = False   '忽略程序跨越线程运行导致的错误。   
        t = New Thread(AddressOf testA)  '创建线程,使它指向test过程,注意该过程不能带有参数
        t.Start() '启动线程
    End Sub

也可以针对某类控件进行设置,例如:

Button.CheckForIllegalCrossThreadCalls = false

再次运行程序,就不会有错了,你还能看见动态变化,并且没有“假死”。如上就是线程的好处。

但是:
CheckForIllegalCrossThreadCalls = False
一句跨线程调用Windows窗体控件就万能了吗?毕竟这种方式很不优秀。

CheckForIllegalCrossThreadCalls 容许子线呈随时更新ui,在同一个test函数体内,不能保证自身事务的一致性。给 Label1 付了值,一回头就已经被别人改了,这是多么无语和暴走心情。  如果你觉的你的应用不会考虑在写入ui的同时来读取ui,而倾向使用CheckForIllegalCrossThreadCalls来追求效率的话,也是不恰当的做法。

首先 CheckForIllegalCrossThreadCalls 并不能让效率发生本质的变化。 其次需求永远是变化的,现在不考虑不等于以后不会碰到

当然你可以自己加锁,用信号量,这样还不如直接使用Invoke了,你只是又把别人做好的事情做了一遍。

不然,请看下文。

我希望通过Form1的按钮,让Form2中的Label0显示0~9000.

代码如下:

Imports System.Threading
Public Class Form1
    Dim t As Thread
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Form2.Show()
        Button.CheckForIllegalCrossThreadCalls = False
        t = New Thread(AddressOf testA)
        t.Start()
    End Sub
    Private Sub testA()
        For i = 0 To 9000
            Form2.Label1.Text = i.ToString '注意,这里改成 Form2 窗体上的标签显示
        Next
        t.Abort()
    End Sub
End Class

运行试试,咦?Form2里面怎么没变?如图:


 难道没有执行那句代码?
添加断点看看?很明显执行了。但是就是没显示,程序不听话了?
CheckForIllegalCrossThreadCalls = False 没辙了吗?

看后文。

跨两个UI调用CheckForIllegalCrossThreadCalls = False 
确实不太给力,那么如何是好?

这里,我们就要用到“委托”和 invoke? 什么东东啊? 往后看。。。
在 Form1 里面添加委托声明代码(带一个参数),和控件更新过程(带一个参数),
在 testA 中使用 Me.Invoke 调用委托,执行UpdateUI,并向里面传一个参数 i
稍微修改一下,其余代码不变:

Imports System.Threading
Public Class Form1
    Dim t As Thread
    Public Delegate Sub ToThread(ByVal setValue As Integer)  '声明一个公开带整形参数的委托
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Form2.Show()
        Button.CheckForIllegalCrossThreadCalls = False
        t = New Thread(AddressOf testA)
        t.Start()
    End Sub
    Private Sub testA()  '委托人,委托 UpdateUI 方法把我的结果告诉窗体2中的标签,让它显示
        For i = 0 To 9000
            Dim ivo As New ToThread(AddressOf UpdateUI)  '实例化委托,并指向被委托的方法
            Invoke(ivo, i)  '用 Invoke 调用委托,并传递参数      
        Next
        t.Abort()
    End Sub

'中间人、媒介人、被委托人(方法),代替textA 去告诉窗体2中的标签,让它把 TextA 事件传递过来的结果显示出来。
    Private Sub UpdateUI(ByVal value As Integer) 
        Form2.Label1.Text = value.ToString
    End Sub
End Class

下面运行试试?你看到了什么?是不是动态变化了哦?

删除这一句:CheckForIllegalCrossThreadCalls = False 也行。

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Form2.Show()
        t = New Thread(AddressOf testA)
        t.Start()
    End Sub

如图:

那么,像这样跨线程调用Windows窗体控件就实现了,并且这是被允许的安全方法。有了线程和委托的联合,我们就能创建更加人性化的程序了,快速而又安全。多线程的实现就是开很多线程罢了,记住最后一定要.Abort关闭线程,不然如果线程未结束,程序退出只是UI退出,线程还在呢....

VB.NET 初涉线程的定义和调用的更多相关文章

  1. C# 线程的定义和使用

    C# 线程的定义和使用 一.C# Thread类的基本用法 通过System.Threading.Thread类可以开始新的线程,并在线程堆栈中运行静态或实例方法.可以通过Thread类的的构造方法传 ...

  2. 12_传智播客iOS视频教程_注释和函数的定义和调用

    OC的注释和C语言的注释一模一样.它也分单行注释和多行注释. OC程序里面当然可以定义一个函数.并且定义的方式方法和调用的方式方法和我们C语言是一模一样的.OC有什么好学的?一样还学个什么呢? 重点是 ...

  3. GC、进程和线程的定义

    GC是什么,为什么要有GC GC是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃.Java提供的GC ...

  4. javascript 内部函数的定义及调用

    内部函数:定义在另一个函数中的函数 例如: <script> function outer(){ function inner(){ } } </script> inner() ...

  5. O-C相关04:类方法的概述与定义和调用

    类方法的概述与定义和调用 1, 类方法的概述 类方法(class method)在其他编程语言中常常称为静态方法(例如 Java 或 C# 等). 与实例方法不同的是,类方法只需要使用类名即可调用, ...

  6. .NET:线程本地存储、调用上下文、逻辑调用上下文

    .NET:线程本地存储.调用上下文.逻辑调用上下文 目录 背景线程本地存储调用上下文逻辑调用上下文备注 背景返回目录 在多线程环境,如果需要将实例的生命周期控制在某个操作的执行期间,该如何设计?经典的 ...

  7. Python基础--函数的定义和调用

    一.函数的作用: 提高代码的可读性,减少代码的冗余,方便调用和修改,组织结构清晰 二.函数的定义:函数遵循先定义后调用的原则 1.无参函数 def funcname(): #def 是关键字,后跟函数 ...

  8. Linux Shell函数定义与调用

    一.Shell函数定义格式 shell函数定义格式,各部分说明如下: [ function ]等中括号括起来部分----表示可选(即可有可无) your_function_name部分----为函数名 ...

  9. Python函数的定义与调用、返回值、参数

    一.函数是什么 函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段. 函数能提高应用的模块性,和代码的重复利用率.比如print(),len()等.但你也可以自己创建函数,这被叫做用户自 ...

随机推荐

  1. 网页 php开发中html空文本节点问题user agent stylesheetbody

    最近开发中遇到一个奇怪的问题,我的一个网站头部,代码固定不变,放在了不同的模板进行展示,一部分出现了问题,总是距离相差8个像素,用firebug查看发现:meta 跑到 body 下面去了,并且发现了 ...

  2. PHP学习笔记十三【二维数组】

    <?php //二维数组 $arr=array(array(1,2,3),array(4,5,6)); $arr1[0]=array(12,34,65); $arr1[1]=array(34,6 ...

  3. linux学习笔记之IO

    一.基础知识. 1:普通IO类型. 1,非阻塞IO:发出open/read/write等IO操作,并使这些操作不会永远阻塞.当不能完成时,会立即出错返回. 1)非阻塞的两种标志方式:指定标志:O_NO ...

  4. IE8的项目在IE11下 一些功能无法实现的解决方案

    最近改了一些IE11下一些功能无法实现的项目,发现了有一些IE8下的方法 ,在IE11下被取消或者替代了,如下: 1.JavaScript 运行时错误: 对象不支持“attachEvent”属性或方法 ...

  5. Android系统信息

    前提:获取的都是AndroidMainfest.xml下的信息 一.PackageManager 负责管理所有已安装的App 二.ActivityInfo 封装了Mainifest中的<acti ...

  6. Lifting the Stone(hdoj1115)

    Lifting the Stone Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others ...

  7. SQL Server 连接和事务相关的问题。

    方法 1. dbcc opentran + sys.dm_exec_connections dbcc opentran; dbcc opentran 针对当前数据库 dbcc opentran('St ...

  8. 局域网内IP冲突怎么办

      对于在Internet和Intranet网络上,使用TCP/IP协议时每台主机必须具有独立的IP地址,有了IP地址的主机才能与网络上的其它主机进行通讯.但IP地址冲突会造成网络客户不能正常工作,只 ...

  9. How to delete the icons of Win7 desktop shortcuts

    1. Copy the following bat code in txt type file, 2. save it as file extension type bat, run it as ad ...

  10. Intuit Quicken Home & Business 2016(Manage your business and personal finances)

    Quicken Home & Business 2016 - Manage your business and personal finances all in one place. Cate ...