VB.NET 初涉线程的定义和调用
什么是线程
说话一:进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.
线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行
说法二:进程和线程都是由操作系统所体会的程序运行的基本单元,系统利用该基本单元实现系统对应用的并发性。进程和线程的区别在于:
简而言之,一个程序至少有一个进程,一个进程至少有一个线程.
线程的划分尺度小于进程,使得多线程程序的并发性高。
另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。
我们一般编写的程序代码总是从 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 初涉线程的定义和调用的更多相关文章
- C# 线程的定义和使用
C# 线程的定义和使用 一.C# Thread类的基本用法 通过System.Threading.Thread类可以开始新的线程,并在线程堆栈中运行静态或实例方法.可以通过Thread类的的构造方法传 ...
- 12_传智播客iOS视频教程_注释和函数的定义和调用
OC的注释和C语言的注释一模一样.它也分单行注释和多行注释. OC程序里面当然可以定义一个函数.并且定义的方式方法和调用的方式方法和我们C语言是一模一样的.OC有什么好学的?一样还学个什么呢? 重点是 ...
- GC、进程和线程的定义
GC是什么,为什么要有GC GC是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃.Java提供的GC ...
- javascript 内部函数的定义及调用
内部函数:定义在另一个函数中的函数 例如: <script> function outer(){ function inner(){ } } </script> inner() ...
- O-C相关04:类方法的概述与定义和调用
类方法的概述与定义和调用 1, 类方法的概述 类方法(class method)在其他编程语言中常常称为静态方法(例如 Java 或 C# 等). 与实例方法不同的是,类方法只需要使用类名即可调用, ...
- .NET:线程本地存储、调用上下文、逻辑调用上下文
.NET:线程本地存储.调用上下文.逻辑调用上下文 目录 背景线程本地存储调用上下文逻辑调用上下文备注 背景返回目录 在多线程环境,如果需要将实例的生命周期控制在某个操作的执行期间,该如何设计?经典的 ...
- Python基础--函数的定义和调用
一.函数的作用: 提高代码的可读性,减少代码的冗余,方便调用和修改,组织结构清晰 二.函数的定义:函数遵循先定义后调用的原则 1.无参函数 def funcname(): #def 是关键字,后跟函数 ...
- Linux Shell函数定义与调用
一.Shell函数定义格式 shell函数定义格式,各部分说明如下: [ function ]等中括号括起来部分----表示可选(即可有可无) your_function_name部分----为函数名 ...
- Python函数的定义与调用、返回值、参数
一.函数是什么 函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段. 函数能提高应用的模块性,和代码的重复利用率.比如print(),len()等.但你也可以自己创建函数,这被叫做用户自 ...
随机推荐
- MVC学习 (一)
在学习MVC之前对asp.net MVC已经有了一些了解,但是还是有很多的疑问,接下来我慢慢来看书学习并带着问题写博客以作记录. 1.MVC是什么? 2.Asp.net MVC和传统的Asp.net ...
- 将mysql服务加入到系统服务中 服务器配置 注销时不会关闭mysql
将mysql加入系统服务中: 或者在cmd中输入:mysql安装路径\mysql\bin\mydqld.exe --install mysql --defaults-file="mysql安 ...
- C#中打日志导出日志到txt文本
/// <summary> /// 打日志 /// </summary> /// <param name="log"></param> ...
- hdu 4612 Warm up(无向图Tarjan+树的直径)
题意:有N个点,M条边(有重边)的无向图,这样图中会可能有桥,问加一条边后,使桥最少,求该桥树. 思路:这个标准想法很好想到,缩点后,求出图中的桥的个数,然后重建图必为树,求出树的最长直径,在该直径的 ...
- windows----composer、laravel安装
环境要求:php+apache,并且将php的根目录配置到“环境变量”. 1.安装composer 使用cmd进入想要安装composer的目录执行如下命令,下载composer.phar文件: ph ...
- yii2不用composer使用redis
1.下载redis https://github.com/yiisoft/yii2-redis 2.下载解压放到 basic\vendor\yiisoft\yii2-redis 3.编辑文件: bas ...
- python运维开发(十三)----SQLalchemy和paramiko续
内容目录: ORM架构SQLalchemy Paramiko SQLalchemy对表的操作 使用 ORM/Schema Type/SQL Expression Language/Engine/Con ...
- Android上使用OpenglES2.0遇到的一点问题
按照教程开发OpenglES2.0应用,遇到Logcat报错“Called unimplemented OpenGL ES API” 在论坛和stackoverflow上找到了答案. 1.manife ...
- PHP环境搭配
电脑上如果有apache,必须先卸载了先,如果有集成的环境,类似于apmserver,也必须先停止先.不然安装的时候,会出现修复和卸载选项,而不是典型安装跟用户自定义安装. apache安装目录 E: ...
- SQL Server 查看表定义的 2 种方法
方法 1. 用SQL Server Management Studio 第一步找到要查看的表,右键 第二步点设计 方法 2. sp_help @objname = 'tableName' execut ...