关于VB.net中线程Thread类与线程池的探讨
vb.net吧
全部回复
仅看楼主
level 7
youki_xwy 楼主
最近自己总结了多线程的概念,网上VB.net原码非常少,大部分都是C#的。希望在这里和有兴趣的朋友一起分享,有错误的地方希望大家能指出,以下均为原创.
对于已经习惯用多线程编写程序的朋友来说这些概念应该是不陌生的,在这里我首先先强调下基础概念。
一.基础概念
1. 首先操作系统是多任务系统,系统把不同的程序任务安排在不同的系统内存上,每个内存互相独立,存放着资源代码,并且不能互相干扰,这样一个内存区域就是进程,一个进程内至少有一个前台线程,否则进程就会被销毁释放。
2.线程可以理解成一个代码执行流,一般来说一个程序总存在一个主线程,前台线程和后台线程本质区别在于前台线程的生命周期是整个进程的生命周期,如果前台哪怕只有一个线程还在执行,系统都不会撤消进程,会把最后一个前台线程执行完毕后才会销毁。可是后台线程在前台进程释放完后会立即销毁,进程不会为后台线程保留。
3.使用线程的好处,主要是2个方面,大家知道如果在UI线程执行一个死循环或者耗时非常长的程序,UI会非常卡,如果把这些程序放到别的线程,会得到一个非常好的UI体验。另外的作用就是可以并行处理任务,使复杂任务可以多元化处理。
4.线程同步的概念,可以这么说单线程绝对是同步的,因为在一个线程上调用一个方法,程序的控制权会暂时交给该方法,等方法执行完毕后返回,代码才能从调用方法那里继续执行。可是,多线程同步的概念是当一个线程在执行时候,其他线程会被暂时阻塞,当多个线程同时访问一快内存时候,可能会出现不可预料的问题,所以多线程的同步有时候难以避免,这些后面我会举例。
5线程异步概念 ,异步是相对同步而言的,当一个线程里开启另外一个线程时候,这时候调用线程发出请求之后不会等待,立刻返回,继续执行其他任务。
二.多线程开启手段
首先我们必须要了解线程池的概念,大家知道线程是属于系统管理级别的资源,非常昂贵,如果我们随意创建线程,不充分利用的话会损失系统性能,所以.net帮我们做好了一切,.net系统为每个程序进程提供一个线程池,在线程池中系统会自动的判断异步请求的数目来自己自动创建线程,把超时不用的空闲线程自动销毁,为我们编写异步多线程程序带了极大的方便,顺便说下线程池的线程默认都是后台的。
下面我简单介绍下系统线程池的使用方法。
1.通过控件Backgroudworker控件来实现,这个空间使用也是很方便的,他实现了一个后台运行的SUB dowork,这里编写你的后台执行任务,关于该控件的用法,由于篇幅问题不做详细说明,非常简单,但是使用起来不是特别方便。
2.通过System.Timers.Timer和System.Threading.Timer来实现,只是WindowsForm里的定时器是UI线程上执行的,所以不属于异步范畴。
3.通过委托Delegate和System.Threading.ThreadPool.QueueUserWorkItem()静态共享方法来实现,这里2个方法本质是差不多,我这里把委托拿出来重点来说。
delegate是.net里面的定义方法指针的强类型,很多底成功能都要通过委托来完成,这里主要利用委托来进行多线程的演示,首先创建如图界面。
这里2个按钮分别启动2个线程来执行一段烦琐的循环程序,并定期向UI的progressbar控件提供进度信息,最后更新listbox窗口的数据。
为了防止共享数据的干扰,屏除不必要的同步,这里先创建一个Class Sender,该类写在Class form1中,定为私有类即可,代码非常简单,只是存储几个数据
先不要占楼,谢谢
2013年04月24日 07点04分 1
level 7
youki_xwy 楼主
下面我们要定义一个后台执行任务的委托ThreadWork,一个用于更新UI控件的两个委托Report和Play 代码如下
Private Delegate Sub ThreadWork(ByVal obj As Sender)
Private Delegate Sub Play(ByVal obj As ListBox, ByVal data As List(Of Integer)) Private Delegate Sub report(ByVal n As Integer, ByVal obj As ProgressBar)
接下来我们写3个符合委托代理的方法
Private Sub Update(ByVal obj As ListBox, ByVal data As List(Of Integer))
For Each n As Integer In data
obj.Items.Add(n)
Next
End Sub
此方法用来更新ListBox控件数据
Private Sub reportsub(ByVal n As Integer, ByVal obj As ProgressBar)
obj.Value = n
End Sub
此方法用来显示进度
Private Sub worker_DoWork(ByVal obj As Sender)
obj.Data.Clear()
Dim report As New report(AddressOf Me.reportsub)
Dim pstate As Boolean
For n As Integer = 1 To 2000
For x As Integer = 1 To CInt(2000 / 2)
For y As Integer = 1 To x
If x + y = 2 * n
+3
Then
pstate = True
Exit For
End If
Next
If pstate Then
Exit For
End If
Next
If pstate Then
obj.Data.Add(n)
pstate = False
End If
Me.BeginInvoke(report, CInt((n / 2000) * 100), obj.Bar)
Next
End Sub
这里的这个方法很幼稚,只是单纯的用多循环来拖延时间
2013年04月24日 07点04分 2
赶上直播了!楼主加油
2013年04月24日 07点04分
level 7
youki_xwy 楼主
接这写按钮Click事件
题外话,这里简单介绍下调用动态链接库的方法,调用win32下"user32.dll"中的消息对话框
在最上面输入代码
Imports System.Runtime.InteropServices
'this is only a test
<DllImport("user32.dll", EntryPoint:="MessageBox")> Public Shared Function ShowBox(ByVal h As Integer, ByVal str As String, ByVal cap As String, Optional ByVal type As Integer = 0) As Int32
End Function
这里整合了,2个按钮都是启动向线程池发送请求异步操作,所以可以用一个方法来响应2个按钮的事件
Private Sub BtnClick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click, Button2.Click
Dim work As New ThreadWork(AddressOf Me.worker_DoWork)
Dim result As IAsyncResult
Dim obj As Sender
Select Case CType(sender, Button).Text
Case "start_1"
obj = New Sender(ProgressBar1, ListBox1, New List(Of Integer))
Case "start_2"
obj = New Sender(ProgressBar2, ListBox2, New List(Of Integer))
Case Else
Me.ShowBox(0, "按钮的Text属性设置下吧", "信息提示")
Application.Exit()
End Select
result = work.BeginInvoke(obj, AddressOf Me.tastcomplete, obj)
End Sub
这里BeginInvoke方法里第二个参数是一个委托,他是System.AsyncCallback类型,所以我必须为了封装一个符合参数的方法用与回调,这里当后台任务worker_DoWork完成后会回掉这个方法,他必须满足delegate sub AsyncCallback(ar as IAsyncResult)
所以这里继续创建一个符合参数的方法让他回掉
Private Sub tastcomplete(ByVal ar As IAsyncResult)
Dim update As New Play(AddressOf Me.UpdatePlay)
Dim result As IAsyncResult
Dim obj As Sender = CType(ar.AsyncState, Sender)
result = Me.BeginInvoke(update, obj.List, obj.Data)
result.AsyncWaitHandle.WaitOne()
End Sub
这里的Ar可以用他AsyncState属性返回之见delegate.BeginInvoke封送的最后一个参数,这里还用了AsyncwaitHandle.waitone,来完成异步中的同步等待
由于control类中的控件具有线程亲和性,即控件只能由创建该控件的线程访问,所以更新UI控件必须要UI线程执行,如果尝试用线程池的线程去操作UI控件会出错,必须通过Control类
的BeginInvoke方法把通过委托把要更新UI控件的任务封装给UI线程,相当于给UI线程发送消息,等待UI线程处理。
2013年04月24日 08点04分 3
声明为什么不用declare?用declare可缩短代码长度
2013年04月25日 01点04分
回复 Nukepayload2 :Declare当然是可以的,老VB也是用Declare,但是用DllImportAttribute 属性提供调用非托管函数是.net环境跨平台调用的规范,按微软的意思是鼓励大家都这样做.
2013年04月25日 02点04分
@youki_xwy 其实,编译器编译declare时会转化它为dllimport,不信可用il 反编译器看。调用没问题,转换语言要多费点劲。
2013年04月27日 13点04分
level 7
youki_xwy 楼主
这里的例子是我自己随便写的测试用的,还是能反映问题的
继续介绍异步中同步控制问题,先从简单的开始
假设A线程中启动了另外一个异步任务B,A在给线程池发送请求封送出信息同时立即返回,继续做自己的事,至于线程池什么时候安排线程去执行B,A是不管的,但是有时候我们需要A暂时停止执行,需要等待B执行完毕后才能执行,这时候就需要一些手段来暂时阻塞A线程
注意,千万不要在UI线程组塞当前线程,这会造成UI无响应
1.BeginInvoke或者其他对象提供的BeginXYZ方法会返回一个IAsyncResult类型的参数,这里的参数是个非常重要的,他返回异步执行的状态,可以通过IAsyncResult中的AnsyncWaitHandle属性返回的WaitHandle对象中的waitone()方法了组塞当前线程,他会等待异步执行完毕后才解除当前线程的组塞
2.既然有BeginXYZ,必然就有EndXYZ方法,可以用BeginXYZ返回的参数来调用该方法,效果也是组塞当前线程,和waitone效果一样
3.通过死循环来查询
可以写个 Do While Not result.IsCompleted
Loop
2013年04月24日 08点04分 4
level 7
youki_xwy 楼主
关于线程池也就暂时说到这里,下面介绍Thread类
对于Threading.Thread类,使用这个类必须自己实现线程手动复用,如果使用不好就会浪费资源,大家知道线程池里的线程并没有一个可以直接控制的接口,大部分都是通过IAsyncResult异步操作状态来控制异步,但是功能十分有限制,使用Thread类可以提供丰富的控制方式,但是风险是性能损失都比较高,一般默认情况下,Thread类创建的线程都是前台的,可以设置属性IsBackgraound来改变,该类构造函数接受2个委托实例,需要一个无参数或者带Object类型的参数的方法来匹配
2013年04月24日 09点04分 5
level 7
youki_xwy 楼主
下班了,回来继续
说到Thread类,不得不说下线程创建的原则,理论上把复杂耗时的代码放到支线程是非常有效的,但是如果线程创建多了,那么就存在多线程竞争问题,多线程控制非常烦琐,不建议自己创建太多的现成去控制,尽可能的用线程池的线程排队,这样会使性能提升很多,我个人觉得现成创建必须注意以下几点.
1.监听任务,比如监视控制类板卡的I/O状态,监视某个文件夹,监视USB接口的使用,这些反复循环的程序,最后用后台线程来执行.
2.输出更新,比如更新数据库的程序,最好放在前台现成执行.
3.短时间处理的烦琐任务,最好用现成池
4.整个生命周期为程序周期的任务,用线程池是没有意义的,因为它不会给机会让给别的任务,所以这样的情况比较适合用Thread类创建线程
2013年04月24日 11点04分 6
level 7
youki_xwy 楼主
先介绍下Thread类中的重要成员,比较简单但是很重要的是Name属性,现成的名字,默认是
Nothing;IsBackground属性True表示后台;IsAlive属性,表示是否激活;
然后是方法首先是Start成员,该方法用于启动线程
然后Join成员,该方法用语中断调用线程,使其阻塞,暂停执行,等待某线程执行完毕返回后才解除阻塞,相当于前面的异步中的同步控制
再来看看几个公享成员,首先是Thread.CurrentThread用语获取当前线程
Thread.Sleep用于使当前线程挂起,休眠一段时间
同样借用上面的代码,先把woker_DoWork方法参数改成Object
Private Sub worker_DoWork(ByVal obj As Object)
obj.Data.Clear()
Dim report As New report(AddressOf Me.reportsub)
Dim pstate As Boolean
For n As Integer = 1 To 2000
For x As Integer = 1 To CInt(2000 / 2)
For y As Integer = 1 To x
If x + y = 2 * n+3 Then
pstate = True
Exit For
End If
Next
If pstate Then
Exit For
End If
Next
If pstate Then
obj.Data.Add(n)
pstate = False
End If
Me.BeginInvoke(report, CInt((n / 2000) * 100), Ctye(obj,Sender).Bar)
Next
Dim update As New Play(AddressOf Me.UpdatePlay)
Dim result As IAsyncResult
result = Me.BeginInvoke(update, Ctype(obj,Sender).List, Ctype(obj,Sender).Data)
End Sub
这里没有回掉函数CallBack委托制定的机智,所以我们必须在后台任务结束之前把信息通过委托封送给UI线程,让UI线程抽空闲时候去更新数据
请注意下,这里Dim update As New Play(AddressOf Me.UpdatePlay)
有个错误,就是把之前Update方法的名字改成UpdatePlay,书写错误!
2013年04月24日 12点04分 7
level 7
youki_xwy 楼主

Dim obj As New sender(Me.ListBox2, Me.ProgressBar2, New List(Of Integer))
Dim tThread as New Thread(AddressOf Me.worker_DoWork)
tThread.Name = "副线程"
tThread.Start(obj)
今天先写到这里了,大家可以自己去做做实验,比如把join写到代码中去,你很快就会发现问题,祝大家愉快,有什么问题可以指出大家一起探讨,非常欢迎!
VB.net是门强大的语言,希望大家喜欢
2013年04月24日 12点04分 8
level 11
受教了,有没有好一点的教程推荐下,谢谢
2013年04月24日 13点04分 9
关于线程的高级概念,比如临界区域,同步锁等等,这些掌握起来还是有点难度的,有兴趣的朋友可以自己研究,不过基础概念不能疏忽,先从简单的开始,由浅入深吧!至于教程谈不上啊,只是介绍下我的总结而已,这些代码都很容易看懂,有什么问题一起探讨
2013年04月24日 13点04分
你太谦虚了,我要向你学习
2013年04月24日 14点04分
level 7
youki_xwy 楼主
接下来给大家带来一个同步控制的高级类 Threading.AutoResetEvent
该类的waitone同样是带来调用线程阻塞,但是与join不同的是,join必须等待特定的线程完成任务后反回,比如掉用Thread1.Join,那么调用线程必须等待Thread1完成指定的代理任务后,才能继续执行,但是AutoResetEvent是可以自由控制的,而且不用等待任务执行完毕,任意时候都可以反回解除调用线程的阻塞,这里还是通过例子来说明,用法非常简单,继续借用之前的代码
在Class1内声明一个成员变量Private en As New Threading.AutoResetEvent(False)
这里为了方便直接实例化
修改之前按狃代码
Private Sub BtnClick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click, Button2.Click
Dim work As New ThreadWork(AddressOf Me.worker_DoWork)
Dim result As IAsyncResult
Dim obj As Sender = Nothing
Select Case CType(sender, Button).Text
Case "start_1"
obj = New Sender(ProgressBar1, ListBox1, New List(Of Integer))
Case "start_2"
obj = New Sender(ProgressBar2, ListBox2, New List(Of Integer))
Case Else
PCI_Server.Show(0, "按钮的Text属性设置下吧", "信息提示")
Application.Exit()
End Select
result = work.BeginInvoke(obj, AddressOf Me.tastcomplete, obj)
en.WaitOne() '这里的添加一行新代码
End Sub
接着修改后台的worker_DoWork方法
Private Sub worker_DoWork(ByVal obj As Sender)
obj.Data.Clear()
Dim report As New report(AddressOf Me.reportsub)
Dim pstate As Boolean
For n As Integer = 1 To 2000
For x As Integer = 1 To CInt(2000 / 2)
For y As Integer = 1 To x
If x + y = 2 * n + 3 Then
pstate = True
Exit For
End If
Next
If pstate Then
Exit For
End If
Next
If pstate Then
obj.Data.Add(n)
pstate = False
End If
Me.BeginInvoke(report, CInt((n / 2000) * 100), obj.Bar)
If n = 1000 Then '请注意这里添加的代码
en.Set()
End If
Next
End Sub
这里还是用委托代理的方法向后台线程池发送异步请求,所以执行完还是会回掉
tastcomplete方法,只不会运行后,你会发现界面会卡住一会,等不卡时候你会发现进度已经有一半,接下来很顺畅的显示
这里的en.Set()就是恢复UI线程的阻塞,只不过不用等待工作处理结束,我们可以在任何地方解除同步,这样可以得到一个更精确的控制,其实只要避开临界区就可以解除同步了,所以这里让循环工作做到一半就让UI现成恢复了工作。
2013年04月25日 07点04分 10
level 2
谢谢分享 这种文章这里太少了
2013年04月27日 14点04分 11
level 1
好厉害啊
2013年04月28日 16点04分 12
level 3
LZ有没有试过用Lambda构建的Parallel? 不过LZ的讲解十分细致, 现在多半的thread教程都是C#的, VB十分少见
2013年05月06日 03点05分 14
vb2010已经支持匿名子程序和多行匿名函数,不过要在VS2010之前版本用VB构建Parallel你不得不在一行里写好几个匿名函数的时候,那语法结构不太好,有点笨拙¬ 这方面经验不足,给予不了什么好建议~[88]
2013年05月06日 05点05分
level 1
楼主你好,我刚学vb.net,您有空了帮我看下下面这个代码
Imports System.Net
Imports System.Net.Sockets
Imports System.Threading
Imports System.Text
Imports System.Diagnostics
Public Class frmServer
'服务器端的Socket
Dim Clistener As Socket
Dim Slistener As Socket
'与客户端会话的Socket
Dim CmySocket As Socket
Dim SmySocket As Socket
'服务器端的运行状态
Dim IsRun As Boolean = False
'监听接收数据线程
Dim CmyThread As Thread
Dim SmyThread As Thread
Dim have As Integer
Public Shared flag As Integer
Public Shared bts() As Byte
Public Shared bl As Integer
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
CmyThread = New Thread(AddressOf Listen)
SmyThread = New Thread(AddressOf SListen)
CmyThread.Start()
SmyThread.Start()
End Sub
Public Sub Listen()
Dim cbytes() As Byte = New [Byte](1024) {}
bts = New [Byte](1024) {}
'Dim data As String = Nothing
Dim ClocalEndPoint As New IPEndPoint(IPAddress.Parse("1.1.1.10"), 502)
'初始化socket
Clistener = New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
'绑定端口
Clistener.Bind(ClocalEndPoint)
'开始监听
Clistener.Listen(10)
BeginInvoke(New EventHandler(AddressOf AddInfo), "服务器端已启动,正在等待连接......")
CmySocket = Clistener.Accept()
IsRun = True
While True
Dim bytesRec As Integer = CmySocket.Receive(cbytes)
' bl = bytesRec
flag = 12
'Dim judge As Byte = cbytes(0)
Dim cc As Integer = cbytes(11)
Dim sc As Integer = cc * 2 + 8
Dim ccbytes() As Byte = New [Byte](sc) {}
Dim ctmp As Integer = readReg(bts, bytesRec, ccbytes, sc)
CmySocket.Send(ccbytes)
End While
End Sub
Public Sub SListen()
Dim sbytes() As Byte = New [Byte](1024) {}
Dim data As String = Nothing
Dim SlocalEndPoint As New IPEndPoint(IPAddress.Parse("2.2.2.2"), 502)
'初始化socket
Slistener = New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
'绑定端口
Slistener.Bind(SlocalEndPoint)
'开始监听
Slistener.Listen(10)
BeginInvoke(New EventHandler(AddressOf AddInfo), "服务器端已启动,正在等待连接......")
SmySocket = Slistener.Accept()
IsRun = True
While True
Dim SbytesRec As Integer = SmySocket.Receive(sbytes)
'Dim judge As Byte = cbytes(0)
Dim cs As Integer = sbytes(11)
Dim ss As Integer = cs * 2 + 8
Dim ssbytes() As Byte = New [Byte](ss) {}
Dim stmp As Integer = readReg(sbytes, SbytesRec, ssbytes, ss)
SmySocket.Send(ssbytes)
End While
End Sub
'发送信息
'Private Sub btnSend_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSend.Click
' Dim msg As Byte() = Encoding.UTF8.GetBytes("Chat|" + TextBox1.Text)
' CmySocket.Send(msg)
' ListBox1.Items.Insert(0, "本地信息: " + TextBox1.Text)
' TextBox1.Text = ""
'End Sub
'非UI线程调用窗体控件,保证线程安全。与聊天实现无关
Sub AddInfo(ByVal sender As System.Object, ByVal e As System.EventArgs)
ListBox1.Items.Insert(0, "远程信息: " + sender.ToString)
End Sub
'关闭窗口时发关退出信息并清理资源
Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
If IsRun Then
Dim msg As Byte() = Encoding.UTF8.GetBytes("Exit|服务器端退出: " + Me.Handle.ToString)
Dim bytesSent As Integer = CmySocket.Send(msg)
End If
Clistener.Close()
Slistener.Close()
CmySocket = Nothing
SmySocket = Nothing
CmyThread.Abort()
SmyThread.Abort()
End Sub
End Class
现在,这个能实现双向通信,但是我想让其中一个进程接收的信息通过另一个进程发送出去,这个要怎么实现?
2018年08月11日 08点08分 15
1