在vb.net中生成多个线程



我有一个应用程序,它可以在网络上搜索文件,然后将它们上传到云服务。这不是最快的事情,所以我开始怀疑我是否应该最终试着去思考线程。

我想做的是让一个线程在网络中爬行,寻找要处理的文件并将其添加到队列中,然后让多个上传线程读取队列,如果有工作要做,他们会将任务从队列中删除。

让多个线程处理上传的原因是这部分过程很慢,根据最初的测试,整个批次可能需要几天甚至几周的时间。目前,在没有任何线程的情况下,我有一个例程,可以找到所有要上传的文件,然后由上传程序上传它们。

因此,我假设,如果我可以以某种方式拥有一个所有线程都可以访问的单个队列/列表,那么向列表中添加文件的线程可以这样做,同时多个上传线程将它们从列表中删除并单独处理。这听起来应该是可能的,但我不知道怎么可能。顺便说一句,这个测试代码中的大多数"睡眠"语句纯粹是为了测试目的。

我发现这个样本很有帮助:https://cjhaas.com/2009/06/25/creating-a-simple-multi-threaded-vb-net-application/

我的测试项目就是基于此。

下面的代码已经完成,但您需要创建一个带有进度条、按钮和标签的表单。它构建和工作良好,包括3个线程,一个用于向队列添加内容(目前为伪数据(,一个处理队列(目前只有一个计时器(,另一个用于监控进度并更新进度条。

我很高兴我已经走到了这一步,因为我是一个完全的新手,但我不知道我需要对这个代码做什么,使它产生多个工作线程,并能够共享同一个队列;我尝试将队列剥离为一个单独的类,但在让线程与队列类通信时出现了问题(我没有注意到这些问题,否则为了完整性我会添加它们(。

有没有一些简单的更改可以让我有多个上传工作线程?或者有没有更好的例子可以让我适应自己的需求?

Option Explicit On
Option Strict On
Imports System.Threading

Public Class Form1
Private MonitorThread As Thread
Private WorkerThread As Thread
Private QueueThread As Thread
Private W As Worker
Private Delegate Sub UpdateUIDelegate()
Private Delegate Sub WorkerDoneDelegate()
Private Sub Monitor()
Do While WorkerThread.ThreadState <> ThreadState.Stopped    'Loop until the Worker thread (and thus the Worker object's Start() method) is done
UpdateUI()                                      'Update the progress bar with the current value
Thread.Sleep(250)                                       'Sleep the monitor thread otherwise we'll be wasting CPU cycles updating the progress bar a million times a second
Loop
WorkerDone()                                                'If we're here, the worker object is done, call a method to do some cleanup
End Sub
Private Sub UpdateUI()
If Me.InvokeRequired Then                                                           'See if we need to cross threads
Me.Invoke(New UpdateUIDelegate(AddressOf UpdateUI), New Object() {})    'If so, have the UI thread call this method for us
Else
'Me.ProgressBar1.Value = curIndex                                                'Otherwise just update the progress bar
'Me.ProgressBar1.Value = W.CurRun                                                'Otherwise just update the progress bar
Me.ProgressBar1.Maximum = W.Qtotal
Me.ProgressBar1.Value = W.Qtotal - W.Remaining                                                'Otherwise just update the progress bar

Me.Lbl_CurrentRun.Text = (W.Qtotal - W.Remaining).ToString
Me.Lbl_Total.Text = "Total = " + W.Qtotal.ToString
Me.Lbl_Remaining.Text = "Remaining = " + W.Remaining.ToString
Me.Lbl_Date.Text = "Date = " + W.MSGdate.ToLongTimeString
Me.Lbl_Path.Text = "Message = " + W.MSGpath
End If
End Sub

Private Sub WorkerDone()
If Me.InvokeRequired Then                                                           'See if we need to cross threads
Me.Invoke(New WorkerDoneDelegate(AddressOf WorkerDone))                         'If so, have the UI thread call this method for us
Else
Me.Button1.Enabled = True                                                       'Otherwise just update the button
Me.ProgressBar1.Value = W.Qtotal - W.Remaining                                  ' Update these to ensure they show the final data
Me.Lbl_CurrentRun.Text = (W.Qtotal - W.Remaining).ToString
Me.Lbl_Remaining.Text = "Remaining = " + W.Remaining.ToString
End If
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Me.Button1.Enabled = False                      'Disable the button
W = New Worker()                                'Create our Worker object
QueueThread = New Thread(AddressOf W.PopulateQueue) 'Create a queue thread and tel it where to start when we call the start method
WorkerThread = New Thread(AddressOf W.Start)    'Create our Worker thread and tell it that when we start it it should call our Worker's Start() method
MonitorThread = New Thread(AddressOf Monitor)   'Create our Monitor thread and tell it that when we start it it should call this class's Monitor() method
QueueThread.Start()                             'Start the queue thread which adds items to the queue
'Wait a while to allow some items to be added to the queue
System.Threading.Thread.Sleep(500)
WorkerThread.Start()                            'Start the worker thread
MonitorThread.Start()                           'Start the monitor thread which updates the progress bar
End Sub
End Class
Public Class Worker
Private iQtotal As Integer = 0            ' The total items processed
Private iRemaining As Integer           ' Remaining items in the queue
Dim SortedList As New List(Of sMSG)()
Public ReadOnly Property Qtotal() As Integer
Get
Return Me.iQtotal
End Get
End Property
Public ReadOnly Property Remaining() As Integer
Get
'SortedList.TrimExcess()
Return SortedList.Count
End Get
End Property
Public ReadOnly Property MSGdate() As Date
Get
Return SortedList.FirstOrDefault.mDate
End Get
End Property
Public ReadOnly Property MSGpath() As String
Get
Return SortedList.FirstOrDefault.sFullpath
End Get
End Property

Public Sub PopulateQueue()
Dim tempMSG As New sMSG         'We will assemble the message in here before adding to the queue
tempMSG.sFullpath = "Path"
tempMSG.sLocation = "Location"
tempMSG.sLocationGUID = "GUID"
tempMSG.sLocationServerID = "666"
' This loop adds test data to the queue. It will be replaced with file system crawler
For i = 1 To 50
tempMSG.mDate = Date.Now
tempMSG.sFullpath = "Path " + i.ToString
SortedList.Add(tempMSG)
iQtotal = iQtotal + 1
Threading.Thread.Sleep(50)
SortList()
Next i
End Sub
Public Sub SortList()
SortedList = (From obj In SortedList Select obj Order By obj.mDate Descending).ToList()
End Sub
Public Sub New()
End Sub
Public Sub Start()
Dim nextMSG As sMSG
While SortedList.Count > 0
nextMSG = SortedList.FirstOrDefault             'Get the first item off the list
'' Do the upload of it...
System.Threading.Thread.Sleep(150)
SortedList.Remove(nextMSG)    'Remove it from the queue
End While
End Sub
End Class

Public Class sMSG
Public sFullpath As String
Public sLocation As String
Public sLocationGUID As String
Public sLocationServerID As String
Public mDate As Date
End Class

#######

因此,根据Craig和djv的建议,我开始考虑更改代码以使用ConcurrentBag对象。

遗憾的是,无法使用ConcurrentBag从列表/队列中删除项目。幸运的是,我遇到了ConcurrentQueue,它运行得很好。

下面的代码不太好看,但它很有效,在我的测试中,我能够看到额外线程的性能改进。它可能需要更多的工作来管理产生了多少线程,但这是以后的工作。

我感谢@Craig和@djv的帮助,我希望这对某人有用。

Option Explicit On
Option Strict On
Imports System.Threading
Imports System.Collections.Concurrent
Public Class Form1
Private MonitorThread As Thread
Private WorkerThread1 As Thread
Private WorkerThread2 As Thread
Private WorkerThread3 As Thread
Private QueueThread As Thread
Private Delegate Sub UpdateUIDelegate()
Private Delegate Sub BatchIsDOneDelegate()
Private tStart As Date
Private cq As New ConcurrentQueue(Of sMSG)
Public iQTotal As Integer

Private Sub Monitor()
Do While cq.Count > 0    'Loop until the queue is empty
UpdateUI()                                      'Update the progress bar with the current value
Thread.Sleep(250)                                       'Sleep the monitor thread otherwise we'll be wasting CPU cycles updating the progress bar a million times a second
Loop
BatchIsDOne()                                                'If we're here, the batch is done, call a method to do some cleanup
End Sub
Private Sub UpdateUI()
If Me.InvokeRequired Then                                                           'See if we need to cross threads
Me.Invoke(New UpdateUIDelegate(AddressOf UpdateUI), New Object() {})    'If so, have the UI thread call this method for us
Else
Me.ProgressBar1.Maximum = iQTotal
Me.ProgressBar1.Value = iQTotal - cq.Count                                                'Otherwise just update the progress bar
Me.Lbl_CurrentRun.Text = (iQTotal - cq.Count).ToString
Me.Lbl_Total.Text = "Total = " + iQTotal.ToString
Me.Lbl_Remaining.Text = "Remaining = " + cq.Count.ToString
Me.Lbl_Elapsed.Text = "Elapsed = " + (Date.Now - tStart).ToString
End If
End Sub

Private Sub BatchIsDOne()
If Me.InvokeRequired Then                                                           'See if we need to cross threads
Me.Invoke(New BatchIsDOneDelegate(AddressOf BatchIsDOne))                         'If so, have the UI thread call this method for us
Else
Me.Button1.Enabled = True                                                       'Otherwise just update the button
Me.ProgressBar1.Value = iQTotal - cq.Count                                  ' Update these to ensure they show the final data
Me.Lbl_CurrentRun.Text = (iQTotal - cq.Count).ToString
Me.Lbl_Remaining.Text = "Remaining = " + cq.Count.ToString
End If
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Me.Button1.Enabled = False                      'Disable the button
tStart = Date.Now                               'Note the start time

QueueThread = New Thread(AddressOf PopulateQueue) 'Create a queue thread and tel it where to start when we call the start method
WorkerThread1 = New Thread(AddressOf ProcessNextMSG)    'Create 1st Worker thread and tell it that when we start it it should call our Worker's Start() method
WorkerThread2 = New Thread(AddressOf ProcessNextMSG)    'Create 2nd Worker thread and tell it that when we start it it should call our Worker's Start() method
WorkerThread3 = New Thread(AddressOf ProcessNextMSG)    'Create 2nd Worker thread and tell it that when we start it it should call our Worker's Start() method
MonitorThread = New Thread(AddressOf Monitor)   'Create our Monitor thread and tell it that when we start it it should call this class's Monitor() method
QueueThread.Start()                             'Start the queue thread which adds items to the queue
System.Threading.Thread.Sleep(500)               'Wait a while to allow some items to be added to the queue just for test purposes
WorkerThread1.Start()                            'Start the worker thread
WorkerThread2.Start()                            'Start the worker thread
WorkerThread3.Start()                            'Start the worker thread
MonitorThread.Start()                           'Start the monitor thread which updates the progress bar
End Sub
Public Sub PopulateQueue()
Dim tempMSG As New sMSG         'We will assemble the message in here before adding to the queue
tempMSG.sFullpath = "Path"
tempMSG.sLocation = "Location"
tempMSG.sLocationGUID = "GUID"
tempMSG.sLocationServerID = "666"

' This loop adds test data to the queue. It will be replaced with a file system crawler
For i = 1 To 50
tempMSG.mDate = Date.Now
tempMSG.sFullpath = "Path " + i.ToString
cq.Enqueue(tempMSG)                 ' Add the MSG to the queue
iQTotal = iQTotal + 1
Threading.Thread.Sleep(50)
Next i
End Sub
Public Sub ProcessNextMSG()
Dim nextMSG As New sMSG
' While the queue is not empty, take a message from the top and process it
While cq.IsEmpty = False
If cq.TryDequeue(nextMSG) = False Then
' It failed to get next message from queue
Else
'' Do the uploading of it...
System.Threading.Thread.Sleep(350)              'Just to mimmick the uploading for now
End If
End While
End Sub
End Class
Public Class sMSG
Public sFullpath As String
Public sLocation As String
Public sLocationGUID As String
Public sLocationServerID As String
Public mDate As Date
End Class

我发现ConcurrentQueue正是我所需要的,并已将完整的工作代码粘贴到上面的线程中。感谢@Craig和@djv的帮助。

最新更新