What I am trying to do is, creating an application which executes some action. there should be maximum of 10 threads running.
I have the following code, which works fine. I need to send a parameter to "Somework" procedure. How can I do that?
Module Module1
Sub Main()
Dim Task As New Action(AddressOf SomeWork)
dim I as integer
for i=1 to 20
If RunningThread < 10 Then
Task.BeginInvoke(AddressOf Callback, Nothing)
Threading.Interlocked.Increment(RunningThread)
Else
SyncLock (Lock)
tasks.Enqueue(Task)
End SyncLock
End If
next
Console.ReadLine()
End Sub
Private tasks As New Queue(Of action)
Private RunningThread As Integer
Private Lock As New Object
Dim I As Integer = 0
Private Sub SomeWork()
I += 1
Console.WriteLine(I & " doing some work - begin :: " & Now.ToString)
Threading.Thread.Sleep(10000)
Console.WriteLine(I & " doing some work - end :: " & Now.ToString)
End Sub
Private Sub Callback(ByVal o As Object)
If tasks.Count > 0 Then
Dim Task As Action
SyncLock (Lock)
Task = tasks.Dequeue
End SyncLock
Task.BeginInvoke(AddressOf Callback, Nothing)
Else
Threading.Interlocked.Decrement(RunningThread)
End If
End Sub
End Module
Kindly help.
Thanks
You can achieve your requirements easily using the Task Parallel Library (TPL) using Parallel.ForEach. Use a constructor that allows you to specify a ParallelOptions parameter and set the MaxDegreeOfParallelism to your thread limit.
Related
I'm pretty new to multithreading, but I am trying to go through each listviewitem where subitem 1 is ".." (quequed).. The code I have right now goes through the first 2, but how can I make it continue with the rest? I am currently running two threads.
I have made this little example application to test out multithreading, which I can then apply to my other application once I get a hang of it.. I pretty much want it to go through each item where status is "..", and then it makes the time go up to 10000 until it continues with the other items, until there are none left.. Any help would be appreciated.
Public Class Form1
Dim i As Integer
Dim i2 As Integer
Dim thread As System.Threading.Thread
Dim thread2 As System.Threading.Thread
Public Class CParameters
Public Property LID As Integer
End Class
Private Sub startMe(ByVal param As Object)
Dim p As CParameters = CType(param, CParameters)
Do Until i = 10000
i = i + 1
ListView1.Items(p.LID).SubItems(1).Text = "Running"
ListView1.Items(p.LID).SubItems(2).Text = i
If i >= 10000 Then
ListView1.Items(p.LID).SubItems(1).Text = "OK"
End If
Loop
End Sub
Private Sub startMe2(ByVal param As Object)
Dim p As CParameters = CType(param, CParameters)
Do Until i = 10000
i = i + 1
ListView1.Items(p.LID).SubItems(1).Text = "Running"
ListView1.Items(p.LID).SubItems(2).Text = i
If i >= 10000 Then
ListView1.Items(p.LID).SubItems(1).Text = "OK"
End If
Loop
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
thread = New System.Threading.Thread(AddressOf startMe)
Dim parameters As New CParameters
parameters.LID = "0"
thread.Start(parameters)
'Thread 2
thread2 = New System.Threading.Thread(AddressOf startMe2)
parameters = New CParameters
parameters.LID = "1"
thread2.Start(parameters)
End Sub
End Class
If you're using .NET 4.5 I strongly recommend the Task based approach. This approach makes asynchronous programming so much easier. I recreated the form you described with a ListView. I added a 4th column for the counter.
Public Class CParameters
Public Property LID As Integer
End Class
Private Async Function startThreadsAsync() As Task
'WARNING: Await does not create a new thread. Task.run does.
Await crunchThreadAsync(New CParameters() With {.LID = 0}) 'Create first task
Await crunchThreadAsync(New CParameters() With {.LID = 1}) 'Create second task
Await crunchThreadAsync(New CParameters() With {.LID = 2}) 'Create third task
End Function
Private Sub UpdateListItem(ByVal pid As Integer, ByVal Status As String, ByVal Time As String, ByVal Count As String)
If Not ListView1.InvokeRequired Then 'If on the main ui thread update
Dim lp As ListViewItem = ListView1.Items(pid)
lp.SubItems(0).Text = pid.ToString
lp.SubItems(1).Text = Status
lp.SubItems(2).Text = Time & "ms"
lp.SubItems(3).Text = Count
Else 'If not on the main ui thread invoke the listviews original thread(ui thread)
ListView1.BeginInvoke(Sub()
Dim lp As ListViewItem = ListView1.Items(pid)
lp.SubItems(0).Text = pid.ToString
lp.SubItems(1).Text = Status
lp.SubItems(2).Text = Time & "ms"
lp.SubItems(3).Text = Count
End Sub)
End If
End Sub
Private Async Function crunchThreadAsync(ByVal param As CParameters) As Task(Of Boolean)
'Setting start text
UpdateListItem(param.LID, "Running", "", "0")
Dim cnt As Integer = 0
Dim p As CParameters = param
Dim l As New Stopwatch() 'For displaying total time spent crunching
l.Start()
'We're not leaving the UI thread.'
'Create new thread for crunching
Dim result As Boolean = Await Task.Run(Function()
Do Until cnt = 10000
cnt = cnt + 1
If cnt Mod 1000 = 0 Then
UpdateListItem(param.LID, "", "", cnt.ToString)
End If
Loop
Return True
End Function)
'We're back in the UI thread again.
l.Stop()
UpdateListItem(param.LID, "Finished", l.ElapsedMilliseconds.ToString, cnt.ToString)
End Function
Private Async Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
TextBox1.Text &= "Starting crunch." & Environment.NewLine
Await startThreadsAsync()
TextBox1.Text &= "Finished crunch." & Environment.NewLine
End Sub
I do not update the ui thread every tick of each counter for a few reasons: The amount of updates on the ui thread would lock up the ui. There is no way around this except to decide when it's necessary to actually update the Ui. In this case I do it every 1000 ticks.
Issue
I am using multi-threading inside my application and the way it works is that i have an array that contains 22 string which stand for some file names:
Public ThreadList As String() = {"FSANO1P", "FJBJB1P", "COPOR1P", "FFBIVDP", "FFCHLDP", "FFDBKDP", "FFDREQP", "FFINVHP", "FFJMNEP", "FFPIVHP", "FFUNTTP", "FJBJM1P", "FJBJM2P", "FJBNT2P", "FPPBE9P", "FTPCP1P", "FTTEO1P", "FTTRQ1P", "FJBJU1P", "FTTEG1P", "FFJACPP", "XATXTDP"}
I then loop through the array and create a new thread for each file:
For Each mThreadName As String In ThreadList
Dim mFileImportThread = New FileImportThreadHandling(mThreadName, mImportGuid, mImportDate, Directory_Location, mCurrentProcessingDate, mRegion)
Next
So inside the new thread 'FileImportThreadHandling' it will call a method by starting a new thread:
mThread = New Thread(AddressOf DoWork)
mThread.Name = "FileImportThreadHandling"
mThread.Start()
Then in 'DoWork' it will determine what file is current in question and will run the code related to the file.
After the code has ran for the file I want to report this back to the main thread. Can somebody give me a solution please.
You will need to use Delegates
EDIT:
Take a look at this snippet:
Sub Main()
mThread = New Thread(AddressOf doWork)
mThread.Name = "FileImportThreadHandling"
mThread.Start()
End Sub
Sub doWork()
'do a lot of hard work
workDone(result)
End Sub
Delegate Sub workDoneDelegate(result As Integer)
Sub workDone(abilita As Boolean, Optional src As Control = Nothing)
If Me.InvokeRequired Then
Me.Invoke(New workDoneDelegate(AddressOf workDone), {result})
Else
'here you're on the main thread
End If
End Sub
Here is an example that might give you some ideas. Note the use of Async and Task. You will need a form with two buttons, and a label.
Public Class Form1
Public ThreadList As String() = {"FSANO1P", "FJBJB1P", "COPOR1P", "FFBIVDP", "*******", "FFJACPP", "XATXTDP"}
'note Async keyword on handler
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Button1.Enabled = False
Dim running As New List(Of Task)
For Each tskName As String In ThreadList
'start new task
Dim tsk As Task
tsk = Task.Run(Sub()
For x As Integer = 1 To 10
Dim ct As Integer = x
'simulate code related to the file
Threading.Thread.Sleep(500)
'report to the UI
Me.Invoke(Sub()
Label1.Text = String.Format("{0} {1}", tskName, ct)
End Sub)
Next
End Sub)
Threading.Thread.Sleep(100) 'for testing delay between each start
running.Add(tsk)
Next
'async wait for all to complete
For Each wtsk As Task In running
Await wtsk
Next
Button1.Enabled = True
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
'test UI responsive during test
Label1.Text = DateTime.Now.ToString
End Sub
End Class
Using Actions and a callback to track remaining operations. (Simplified your class for my example)
Public ThreadList As String() = {"FSANO1P", "FJBJB1P", "COPOR1P", "FFBIVDP", "FFCHLDP"}
Private actionCounter As Integer = 0
Private lockActionCounter As New Object()
Sub Main()
Console.WriteLine("Starting...")
For Each mThreadName As String In ThreadList
Dim mFileImportThread = New FileImportThreadHandling(mThreadName)
actionCounter += 1
Call New Action(AddressOf mFileImportThread.DoWork).
BeginInvoke(AddressOf callback, mThreadName)
Next
Console.Read()
End Sub
Private Sub callback(name As IAsyncResult)
Dim remainingCount As Integer
SyncLock lockActionCounter
actionCounter -= 1
remainingCount = actionCounter
End SyncLock
Console.WriteLine("Finished {0}, {1} remaining", name.AsyncState, actionCounter)
If remainingCount = 0 Then Console.WriteLine("All done")
End Sub
Private Class FileImportThreadHandling
Shared r = New Random()
Private _name As String
Public Sub New(name As String)
_name = name
End Sub
Public Sub DoWork()
Dim delayTime = (r).Next(500, 5000)
Console.WriteLine("Doing {0} for {1:0}ms.", _name, delayTime)
Thread.Sleep(delayTime)
End Sub
End Class
I'm trying to get my head around Async and Await. It's going well but one thing I would like clarification on is why there are two return statements in my method. I'm really looking for an explanation of what is actually happening behind the scenes.
I'll post the full code below as it only amounts to around 80 lines. I'm talking about the central method AllSubfolderFiles, which has both Return counter and Return dirsFraction. What's actually happening with these?
Basically, it is a WinForm application that iterates all the files of subfolders, updating a ProgressBar for each iterated subfolder.
Imports System.IO
Public Class frmAsyncProgress
Private Sub frmAsyncProgress_Load(sender As Object, e As EventArgs) Handles MyBase.Load
barFileProgress.Minimum = 0
barFileProgress.Maximum = 100
btnCancel.Enabled = False
End Sub
Private Async Sub btnStart_Click(sender As Object, e As EventArgs) Handles btnStart.Click
If String.IsNullOrWhiteSpace(txtPath.Text) Then
MessageBox.Show("Provide a location first.", "Location")
Exit Sub
End If
Dim sLocation As String = txtPath.Text.Trim()
If Not Directory.Exists(sLocation) Then
MessageBox.Show("Directory doesn't exist.", "Location")
Exit Sub
End If
Dim progressIndicator = New Progress(Of Integer)(AddressOf UpdateProgress)
btnStart.Enabled = False
btnCancel.Enabled = True
lblPercent.Text = "0%"
Dim allFiles As Integer = Await AllSubfolderFiles(sLocation, progressIndicator)
Debug.WriteLine(allFiles.ToString()) 'the number of subfolders iterated
btnStart.Enabled = True
btnCancel.Enabled = False
End Sub
Private Async Function AllSubfolderFiles(location As String, progress As IProgress(Of Integer)) As Task(Of Integer)
Dim dirsTotal As Integer = Directory.GetDirectories(location).Length
Dim dirsFraction As Integer = Await Task(Of Integer).Run(Function()
Dim counter As Integer = 0
For Each subDir As String In Directory.GetDirectories(location)
SubfolderFiles(subDir)
counter += 1
If progress IsNot Nothing Then
progress.Report(counter * 100 / dirsTotal)
End If
Next
Return counter
End Function)
Return dirsFraction
End Function
Private Sub UpdateProgress(value As Integer)
barFileProgress.Value = value
lblPercent.Text = (value / 100).ToString("#0.##%")
End Sub
Private Sub SubfolderFiles(location As String)
'source: http://stackoverflow.com/questions/16237291/visual-basic-2010-continue-on-error-unauthorizedaccessexception#answer-16237749
Dim paths = New Queue(Of String)()
Dim fileNames = New List(Of String)()
paths.Enqueue(location)
While paths.Count > 0
Dim sDir = paths.Dequeue()
Try
Dim files = Directory.GetFiles(sDir)
For Each file As String In Directory.GetFiles(sDir)
fileNames.Add(file)
Next
For Each subDir As String In Directory.GetDirectories(sDir)
paths.Enqueue(subDir)
Next
Catch ex As UnauthorizedAccessException
' log the exception or ignore it
Debug.WriteLine("Directory {0} could not be accessed!", sDir)
Catch ex As Exception
' log the exception or ...
Throw
End Try
End While
'could return fileNames collection
End Sub
End Class
My assessment is that counter is returned and then marshalled back onto the UI thread as dirsFraction, but I'm not convinced by my attempted explanation.
Inside your AllSubfolderFiles function you call Task.Run and pass in an anonymous function that returns with Return counter. AllSubfolderFiles awaits the result of that call and then returns with Return dirsFraction.
So, you have 2 returns in the same function because you have an anonymous function inside your original function. You can move that function out to its own named function which will make it clearer that there are 2 different functions here.
I currently have a program with 4 threads.
4 Threads are "Worker Threads" that each have a dedicated serial port that monitors a dedicated device. So Worker Thread 1 monitors Com port 1, Thread 2 monitors Com Port 2 etc.
All this all working fine. No conflicts.
However, the 4 worker threads all have to send commands to a 5th Comm Port as well, which is a communication link to a device that can powercycle the other devices.
I.E. they all have to share a specific resource, the 5th com port.
When they do send a command to this 5th shared each thread has to wait until the command has finished before continuing.
I've followed the coding example from Dan (thanks!) and tried to form a prototype test code.
This SEEMS to work.
I would appreciate a critical review of the code to see if I'm going in the right direction.
Apologies if I'm not explaining this very well as whilst I've used threads before. Handling a shared resource is new to me. Also I'm just getting to grips with how Stackoverflow works!!
Many thanks
A simplified solution using a shared instance of a resource and a lock.
Public Class Resource
Public Function Read() As String
Return "result"
End Function
End Class
Public Class ResourceUser
Private Shared resourceLock As New Object
Private Shared r As New Resource()
Public Function Read()
Dim res As String
SyncLock resourceLock
res = r.Read()
End SyncLock
Return res
End Function
End Class
Example usage:
Sub Main()
Dim t1 As New Threading.Thread(AddressOf DoSomethingWithResourceUser)
Dim t2 As New Threading.Thread(AddressOf DoSomethingWithResourceUser)
t1.Start()
t2.Start()
End Sub
Private Sub DoSomethingWithResourceUser()
Dim ru As New ResourceUser()
ru.Read()
End Sub
Here's another example more specific to serial comms. It uses a dictionary to keep track of physical comm resources and their relevant locks, so that you can do asynchronous access to different comm ports, but synchronize access to each single comm port.
Sub Main()
Dim c1 As New CommPortThreadSafe("COM1")
Dim c2 As New CommPortThreadSafe("COM2")
Dim c3 As New CommPortThreadSafe("COM1")
Dim t1 As New Threading.Thread(Sub() c1.Read())
Dim t2 As New Threading.Thread(Sub() c2.Read())
Dim t3 As New Threading.Thread(Sub() c3.Read())
' t1 and t3 can't be in critical region at same time
' t2 will be able to run through critical region
t1.Start()
t2.Start()
t3.Start()
End Sub
Public Class CommPort
Public Property Name As String
Public Function Read() As String
Return "result"
End Function
End Class
Public Class CommPortThreadSafe
Private Shared resourceLocks As New Dictionary(Of String, Object)()
Private Shared comms As New Dictionary(Of String, CommPort)()
Private Shared collectionLock As New Object()
Private commPortName As String
' constructor takes the comm port name
' so the appropriate dictionaries can be set up
Public Sub New(commPortName As String)
SyncLock collectionLock
Me.commPortName = commPortName
If Not comms.ContainsKey(commPortName) Then
Dim c As New CommPort()
Dim o As New Object()
c.Name = commPortName
' configure comm port further etc.
comms.Add(commPortName, c)
resourceLocks.Add(commPortName, o)
End If
End SyncLock
End Sub
Public Function Read()
Dim res As String
SyncLock resourceLocks(Me.commPortName)
res = comms(Me.commPortName).Read()
End SyncLock
Return res
End Function
End Class
To address your recent edits:
Threads A would all declare the comm port in the same way. Actually this is a benefit of this pattern (similar to multiton pattern) which works like a singleton when only one comm port is used. This code could be used in all threads:
Dim myCommPort As New CommPortThreadSafe("COM1")
The lock inside the read is going to synchronize access to COM1 because "COM1" (the name of the comm port) is actually the key to the Dictionary<string, object> used for locking. So when any thread reaches this code, keying with the same key, that region will be accessible only to a single thread because they all use the same key.
SyncLock resourceLocks(Me.commPortName)
res = comms(Me.commPortName).Read()
End SyncLock
As you saw, that string is set up in the constructor so as long as all the threads create their object passing the same string to the constructor, they will all have underlying indirect references to the same CommPort. The constructor can only create the instance if the name doesn't already exist in its dictionary:
If Not comms.ContainsKey(commPortName) Then
Dim c As New CommPort()
Here's another example usage with just one comm port:
Sub Main()
Dim ts As New ThreadStart(
Sub()
Dim c As New CommPortThreadSafe("COM1")
For i As Integer = 0 To 99
c.Read()
Next
End Sub)
Dim t1 As New Threading.Thread(ts)
Dim t2 As New Threading.Thread(ts)
Dim t3 As New Threading.Thread(ts)
Dim t4 As New Threading.Thread(ts)
t1.Start()
t2.Start()
t3.Start()
t4.Start()
End Sub
In this example we start 4 threads which each perform the code in the threadstart. There is a loop reading the comm port. If you test this out, you will see that it is thread-safe as long as the entire read takes place inside Read(), which you will need to develop of course. You may have another layer in which you are sending custom commands and waiting for a response. These two actions should both be inside a single SyncLock in each custom function. Thread B should use the same class if it's doing a similar thing.
FORM CODE FOR TESTING
Imports System.Threading
Public Class Form1
Private Worker(4) As jWorker '4 Worker Object
Public myWorkerThread(4) As Threading.Thread '4 runtime threads
Private Checker As jChecker '1 Checking Object
Public myCheckerThread As Threading.Thread ' Thread to check status of Resource
Dim MainThreadResouce As jResourceUser
'Assume the actual serial port is opened here
'so its available for access by the jResource Object.
'Pressing button 1 will start up all the threads
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'setup a another ResourceUser object here so can see one of the shared values from the form.
MainThreadResouce = New jResourceUser
'Setup and start worker threads - basically these do work and regularly
'send commands to a shared resource
For I = 1 To 4
Worker(I) = New jWorker
Worker(I).id = I
myWorkerThread(I) = New Threading.Thread(AddressOf Worker(I).doWork)
myWorkerThread(I).Start()
Next I
'Start Checking thread - regularly checks something in the resource
'please ignore this for now!
Checker = New jChecker
myCheckerThread = New Threading.Thread(AddressOf Checker.dochecking)
myCheckerThread.Start()
End Sub
End Class
========================
WORKER THREAD, MODELLING THE MONITORING THREADS
Imports System.Threading
Public Class jWorker
Private _id As Integer = 0
Private _workCount As Integer = 0
Private resourceUser As jResourceUser
Public workerStatus As String = ""
Public Property id() As Integer
Get
Return _id
End Get
Set(ByVal Value As Integer)
Me._id = Value
End Set
End Property
Public ReadOnly Property count() As Integer
Get
Return _workCount
End Get
End Property
Public Sub New()
resourceUser = New jResourceUser
End Sub
Sub doWork()
workerStatus = "Started"
Do Until False
Thread.Sleep(1000)
_workCount += 1
If _workCount Mod 5 = 0 Then
'THe line below would cause a bottleneck
'As it prevents the system trying to powercycle any other
'modems whilst its doing just one.
'resourceUser.PowerCycle(_id)
Debug.Print("W" & _id & ", Worker - Requesting Power OFF +++++++++++++")
resourceUser.Poweroff(_id)
Debug.Print("W" & _id & ", Worker - Waiting 10 secs for modem to settle")
Thread.Sleep(10000)
Debug.Print("W" & _id & ", Worker - Requesting Power ON")
resourceUser.PowerOn(_id)
Debug.Print("W" & _id & ", Worker - Finished Power cycle ------------")
End If
Loop
End Sub
End Class
===================
RESOURCE USER - TO CONTROL ACCESS TO SHARED RESOURCE
Public Class jResourceUser
'Variables for handling resouce locking
Private Shared resourceLock As New Object
Private Shared _resource As New jResource("Com1", "ON")
'keeps a status of which workers have signalled an OFF or ON via the Resource
'in the form of a string 11213141 (device number (1-4) - 1 for on, 0 for off
Private Shared _powerStatus As String
Public ReadOnly Property PowerStatus As String
Get
Return _powerStatus
End Get
End Property
Public Sub New()
_powerStatus = _resource.PowerState
End Sub
Sub PowerOn(ByVal WorkerID As Integer)
Debug.Print("W" & WorkerID & ", ResouceUser - requesting Lock for Power ON [" & _powerStatus & "]")
SyncLock resourceLock
_resource.TurnOn(WorkerID)
_powerStatus = _resource.PowerState
Debug.Print("W" & WorkerID & ", ResouceUser - Turned On, Device statuses " & _powerStatus & "]")
End SyncLock
End Sub
Sub Poweroff(ByVal WorkerID As Integer)
Debug.Print("W" & WorkerID & ", ResouceUser requesting Lock for Power OFF [" & _powerStatus & "]")
SyncLock resourceLock
_resource.TurnOff(WorkerID)
_powerStatus = _resource.PowerState
Debug.Print("W" & WorkerID & ", ResouceUser - Turned Off, Device statuses [" & _powerStatus & "]")
End SyncLock
End Sub
'Not going to work as it blocks the whole system when it could be
'reseting other modems.
Sub PowerCycle(ByVal WorkerID As Integer)
SyncLock resourceLock
Debug.Print("W" & WorkerID & ", ResourceUser - Requesting Power CYcle LockPower")
_resource.PowerCycle(WorkerID)
_powerStatus = _resource.PowerState
Debug.Print("W" & WorkerID & ", ResourceUser - Power Cycled")
End SyncLock
End Sub
Function CheckState() As String
SyncLock resourceLock
Return _resource.CheckState
_powerStatus = _resource.PowerState
End SyncLock
End Function
End Class
============
RESOURCE - FOR ACTUAL WORK ON SHARED RESOURCE
'THis code would directly handle interactions
'with one specific com port that has already
'been configured and opened on the main thread.
Public Class jResource
Private _ComPort As String
Private _state As String
Private _PowerState As String
Private _CheckState As String
'Record the com port used for this resource
Public Property ComPort() As String
Get
Return _ComPort
End Get
Set(ByVal Value As String)
Me._ComPort = Value
End Set
End Property
'Returns the a particular status of the resouce
Public ReadOnly Property CheckState As String
Get
'here I'd send a few command to the comm port
'pick u the response and return it
Return _state
End Get
End Property
'The connected serial port is used to power cycle serveral devices
'this property returns the state of all those devices.
Public ReadOnly Property PowerState() As String
Get
Return _PowerState
End Get
End Property
Public Sub New(ByVal name, ByRef state)
Me._ComPort = name
Me._state = state
Me._PowerState = "11213141"
Me._CheckState = "ON"
End Sub
'Simulate a off command sent by a worker
'via its resourceUser object
Public Sub TurnOn(ByVal intWorker As Integer)
'simulate some work with the com port
Dim myTimeOut As DateTime
myTimeOut = Now.AddMilliseconds(500)
Do Until Now > myTimeOut
Loop
'Set the status to show that Device is on.
_PowerState = _PowerState.Replace(intWorker & "0", intWorker & "1")
Debug.Print("W" & intWorker & ", Resource - issued TurnON, Device Statuses [" & _PowerState & "]")
End Sub
Public Sub TurnOff(ByVal intWorker As Integer)
'simulate some work
Dim myTimeOut As DateTime
myTimeOut = Now.AddMilliseconds(500)
Do Until Now > myTimeOut
Loop
'Here would send command to Com port
'Set the status to show that Device is Off.
_PowerState = _PowerState.Replace(intWorker & "1", intWorker & "0")
Debug.Print("W" & intWorker & ", Resource - issued TurnOFF, Device Statuses [" & _PowerState & "]")
End Sub
Public Sub PowerCycle(ByVal intWorker As Integer)
Debug.Print("W" & intWorker & ", Resource - issued PowerCycle, Device Statuses [" & _PowerState & "]")
'Here would send command to Com port
'Set the status to show that Device is Off.
_PowerState = _PowerState.Replace(intWorker & "1", intWorker & "0")
Debug.Print("W" & intWorker & ", Resource - issued TurnOFF, Device Statuses [" & _PowerState & "]")
'simulate some work - takes a while for device to turn off
Dim myTimeOut As DateTime
myTimeOut = Now.AddMilliseconds(10000)
Do Until Now > myTimeOut
Loop
'Here would send command to Com port
'Set the status to show that Device is Off.
_PowerState = _PowerState.Replace(intWorker & "1", intWorker & "1")
Debug.Print("W" & intWorker & ", Resource - issued TurnON, Device Statuses [" & _PowerState & "]")
'simulate some work
myTimeOut = Now.AddMilliseconds(10000)
Do Until Now > myTimeOut
Loop
'Here would send command to Com port
End Sub
End Class
I am trying to multi thread my application so as it is visible while it is executing the process, this is what I have so far:
Private Sub SendPOST(ByVal URL As String)
Try
Dim DataBytes As Byte() = Encoding.ASCII.GetBytes("")
Dim Request As HttpWebRequest = TryCast(WebRequest.Create(URL.Trim & "/webdav/"), HttpWebRequest)
Request.Method = "POST"
Request.ContentType = "application/x-www-form-urlencoded"
Request.ContentLength = DataBytes.Length
Request.Timeout = 1000
Request.ReadWriteTimeout = 1000
Dim PostData As Stream = Request.GetRequestStream()
PostData.Write(DataBytes, 0, DataBytes.Length)
Dim Response As WebResponse = Request.GetResponse()
Dim ResponseStream As Stream = Response.GetResponseStream()
Dim StreamReader As New IO.StreamReader(ResponseStream)
Dim Text As String = StreamReader.ReadToEnd()
PostData.Close()
Catch ex As Exception
If ex.ToString.Contains("401") Then
TextBox2.Text = TextBox2.Text & URL & "/webdav/" & vbNewLine
End If
End Try
End Sub
Public Sub G0()
Dim siteSplit() As String = TextBox1.Text.Split(vbNewLine)
For i = 0 To siteSplit.Count - 1
Try
If siteSplit(i).Contains("http://") Then
SendPOST(siteSplit(i).Trim)
Else
SendPOST("http://" & siteSplit(i).Trim)
End If
Catch ex As Exception
End Try
Next
End Sub
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim t As Thread
t = New Thread(AddressOf Me.G0)
t.Start()
End Sub
However, the 'G0' sub code is not being executed at all, and I need to multi thread the 'SendPOST' as that is what slows the application.
Catch ex As Exception
End Try
A very effective way to stop .NET from telling you what you did wrong. Not knowing why it doesn't work is however the inevitable outcome.
Delete that.
Public Class Form1
'This just shows some concepts of threading.
'it isn't intended to do anything
'requires a Button, and two Labels
'
Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles Button1.Click
'starts / stops a test thread
'isRun = 0 no thread running, start one
'isRun = 1 thread running, stop it
If Threading.Interlocked.Read(isRun) = 0L Then
'start thread
Threading.Interlocked.Increment(isRun)
t = New Threading.Thread(AddressOf showTime)
'simple threading app - display time about twice per second
t.IsBackground = True 'from a background thread
t.Start()
Else
'stop thread
Threading.Interlocked.Exchange(isRun, 0L)
t.Join() 'wait for thread to end
Threading.Monitor.Enter(listLock)
intervalList.Clear() 'clear the list
Threading.Monitor.Exit(listLock)
Label1.Text = "Stop"
Label2.Text = ""
End If
End Sub
Dim t As Threading.Thread
Dim intervalList As New List(Of Double)
Dim listLock As New Object
Dim isRun As Long = 0L
Private Sub showTime()
Dim dlgt As New UpdLblDel(AddressOf UpdateLabel) 'delegate for UI access
Dim lastDateTime As DateTime = Nothing
Do
Dim d As DateTime = DateTime.Now
If lastDateTime <> Nothing Then
'record difference of times - check sleep interval
Threading.Monitor.Enter(listLock)
intervalList.Add((d - lastDateTime).TotalMilliseconds)
Threading.Monitor.Exit(listLock)
End If
lastDateTime = DateTime.Now
dlgt.BeginInvoke(d, Nothing, Nothing) 'update the UI - note immediate return
Threading.Thread.Sleep(500) 'sleep for approx. 500 ms.
Loop While Threading.Interlocked.Read(isRun) = 1L
End Sub
Delegate Sub UpdLblDel(ByVal theTime As Object)
Private Sub UpdateLabel(ByVal theTime As Object)
If Threading.Interlocked.Read(isRun) = 1L Then
If Label1.InvokeRequired Then 'prevent cross-thread errors
Label1.BeginInvoke(New UpdLblDel(AddressOf UpdateLabel), theTime)
Exit Sub
Else
Label1.Text = CType(theTime, DateTime).ToString("HH:mm:ss.f") 'show the time from the background thread
End If
If Threading.Interlocked.Read(intervalList.Count) >= 10L Then
'take average
Threading.Monitor.Enter(listLock)
Dim avg As Double = intervalList.Sum / intervalList.Count 'sum all of the intervals / count
intervalList.Clear() 'clear the list
intervalList.Add(avg) 'forward the average
Label2.Text = avg.ToString("n2") 'show average
Threading.Monitor.Exit(listLock)
End If
End If
End Sub
End Class
You have to wrap the method that accesses the UI component in a delegate (it doesn't have to be a named delegate; it can be anonymous or an Action or Func), and then pass that to Me.Invoke, as others have alluded to.
In this example, I'm wrapping the split functionality in a lambda, and assigning that lambda to a variable of type Func(Of String()). I then pass that variable to Me.Invoke.
Public Sub G0()
Dim siteSplitFunc As Func(Of String()) = Function() _
TextBox1.Text.Split(vbNewLine.ToCharArray())
Dim siteSplit As String() = CType(Me.Invoke(siteSplitFunc), String())
For i = 0 To siteSplit.Count - 1
Try
If siteSplit(i).Contains("http://") Then
SendPOST(siteSplit(i).Trim)
Else
SendPOST("http://" & siteSplit(i).Trim)
End If
Catch ex As Exception
'Do something useful
End Try
Next
End Sub
You cannot access UI object directly from a thread.
When you want to read/write a textbox, you have to do it in the UI thread. This can be done by using Invoke. Or better yet, send/receive the information with parameters.
Here's a Delegate and a matching method. You call the method to update the textbox and it figures out if it should proxy the method for you by basically asking form if its on the same thread:
Private Delegate Sub UpdateTextBoxDelegate(ByVal text As String)
Private Sub UpdateTextBox(ByVal text As String)
If Me.InvokeRequired Then
Me.Invoke(New UpdateTextBoxDelegate(AddressOf UpdateTextBox), text)
Else
TextBox2.Text &= text
End If
End Sub
To use it, just change your catch statement to:
If ex.ToString.Contains("401") Then
UpdateTextBox(URL & "/webdav/" & vbNewLine)
End If