I am working in VB.Net and I need to pause one thread as it waits for the other to finish.
I've seen a very close question but can't seem to figure it out (And couldn't comment on that post Pause/Resume loop in Background worker)
My scenario is that I have 2 background workers. Worker1 is passing fileNames to Worker2 which processes the files. I need to pause Worker1 if Worker2 hasn't finished. I.e. Worker1 only releases the next fileName after Worker2 has finished
Any ideas on how to do this?
WORKING CODE AFTER COMMENTS FROM #user1666788
Code below is for the scenario stated above of two background workers where one has to wait for the other to finish before proceeding.
Dim isFinished as boolean
Dim currentFiile as integer
Private Sub StartWork_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles StartWork.Click
bgWorker1.WorkerSupportsCancellation = True
isFinished = True
currentFile = 0
bgWorker1.RunWorkerAsync()
End Sub
Private Sub bgWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bgWorker1.DoWork
If isFinished = False Then
bgWorker1.CancelAsync()
End If
isFinished = False
For i = currentFile To fileNames.Count - 1
Dim fileName As String = fileNames(i)
LoadRules(myValidator.GetFileType(fileName))
If i = fileNames.Count Then bgWorker1.CancelAsync()
Exit Sub
Next
End Sub
Private Function LoadRules(ByVal fileType As String) As Boolean
' Function to load some rules for file processing
Try
' Start Thread for actual file processing using bgworker2
bgWorker2.WorkerSupportsCancellation = True
bgWorker2.RunWorkerAsync()
Return True
Catch ex As Exception
End Try
End Function
Private Sub bgWorker2_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bgWorker2.DoWork
Try
' Do your thing here
' for x is 0 to 1million
' next
' Mark is finished to true
isFinished = True
' Set currentFile
currentFile += 1
' The bgWorker1 is restarted when bgWorker2 has finished.
' Note however that bgWorker1 will "Continue" where it left off due to the property "currentFile"
bgWorker1.RunWorkerAsync()
'++++++++++++++++++++++++++++++++++++
Catch ex As Exception
End Try
End Sub
There you go. Its working as expected. Now need to figure out how to "monitor" progress of writing a file to disk so that I can start off another process after the file has fully been created.....
I had a similar issue as this. from what I've been told by VS it can't be done.
However I found a way around the subroutines for pausing/resuming threads no longer being used.
The easiest way is to check progress, and if it's still working end the call.
Here is an example, imagine S1 as your first thread and S2 as your second.
sub S1()
if(processingfile)then exit sub
'Insert code here for sending next file for processing
end sub
sub S2()
processingfile = true
'Insert code for processing files
processingfile = false
end sub
That is more or less how I worked around the problem. I hope my advice helped :)
Oh and one more thing, you might want to sleep the first thread before checking if the file is processing so that it doesn't use up a bunch of CPU power. But that's just a guess, I haven't tried it without a short sleeping period
Related
I’ve got this issue with stopping a thread cleanly. I’ve tried to simplify it into a more basic version of the code below and I’m wondering if my approach is completely wrong here.
I have Form1 with a bunch of UI elements which need updating as BackgroundCode runs (I run it here so it’s a separate thread and it doesn’t hold up the UI) I then update the UI by invoking a sub
(Me.Invoke(Sub()
something.property=something
End Sub))
I’m also trying to handle some errors handed to the application by an external file. I’ve used a timer to check for the file and if it exists I grab the contents and pass it to my ErrorHandler. This Writes the Error out to a log file, displays it on screen and then aborts the background worker so that the program doesn’t continue to run. The trouble I’m getting is that by executing BackgroundThread.Abort() that action itself is triggering the ErrorHandler. Is there a way to ask the BackgroundThread to stop cleanly? I want BackgroundThread to trigger the ErrorHandler if something else goes wrong in that code.
I’m wondering about using a global boolean like “ErrorIsRunning” to restrict the ErrorHandler sub so that it can only ever run once, but this is starting to feel more and more hacky and I’m wondering if I’ve gone completely off track here and if there might be a better way to approach the entire thing.
Public Class Form1
Dim BackgroundThread As New Thread(AddressOf BackgroundCode)
Public Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
‘Hide Error Page
ErrorPage.Visible = False
ErrorLabel.Visible = False
‘Start Background Code
BackgroundThread.Start()
End Sub
Private Sub BackgroundCode()
Try
‘<Background code which runs over a number of minutes>
Catch.ex as Exception
ErrorHandler(“Error with BackgroundCode: “ + ex.Message)
End Try
End Sub
Private Sub Timer_Tick(sender As Object, e As EventArgs) Handles Timer.Tick
Dim ErrorFile As String = “C:\MyErrorFile.Err”
Dim ErrorContents As String
If File.Exists(ErrorFile) Then
Timer.Enabled = False
ErrorContents = File.ReadAllText(ErrorFile).Trim()
ErrorHandler(ErrorContents)
End If
End Sub
Public Sub ErrorHandler(ErrorText As String)
WriteLog(“ERROR” + ErrorText)
Me.Invoke(Sub()
Me.ErrorPage.Visible = True
Me.ErrorLabel.Text = ErrorText
End Sub)
BackgroundThread.Abort()
End Sub
End Class
Never abort threads.
This uses a Task and a ManualResetEvent. Without seeing the code inside of the background task it is hard to know how many stop checks might be needed.
Public Class Form1
Private BackgroundTask As Task
Private BackgroundTaskRunning As New Threading.ManualResetEvent(True)
Public Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'Hide Error Page
ErrorPage.Visible = False
ErrorLabel.Visible = False
'Start Background Code
BackgroundTask = Task.Run(Sub() BackgroundCode())
End Sub
Private Sub BackgroundCode()
Try
'<Background code which runs over a number of minutes>
'put stop checks periodically
' e.g.
If Not BackgroundTaskRunning.WaitOne(0) Then Exit Sub 'stop check
Catch ex As Exception
ErrorHandler("Error with BackgroundCode: " + ex.Message)
End Try
End Sub
Private Sub Timer_Tick(sender As Object, e As EventArgs) Handles Timer.Tick
Dim ErrorFile As String = "C:\MyErrorFile.Err"
Dim ErrorContents As String
If File.Exists(ErrorFile) Then
Timer.Enabled = False
ErrorContents = File.ReadAllText(ErrorFile).Trim()
ErrorHandler(ErrorContents)
End If
End Sub
Public Sub ErrorHandler(ErrorText As String)
WriteLog("ERROR" + ErrorText)
Me.Invoke(Sub()
Me.ErrorPage.Visible = True
Me.ErrorLabel.Text = ErrorText
End Sub)
BackgroundTaskRunning.Reset() 'stop task <<<<<<<<<<<<<<<<<<<<<<<<<<<
End Sub
End Class
I have the following code:
Dim p() As Process
Private Sub CheckIfRunning()
p = Process.GetProcessesByName("skype") 'Process name without the .exe
If p.Count > 0 Then
' Process is running
MessageBox.Show("Yes, Skype is running")
Else
' Process is not running
MessageBox.Show("No, Skype isn't running")
End If
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
CheckIfRunning()
End Sub
And it works GREAT!
But I'm wondering how I would convert this to a monitoring application, to constantly check if the processes is running. Is it as simple as putting the check on a timer every 1 second, or is there a better, more efficient way to go about this.
In the end result, I'd like to have a label that says "Running", or "Not Running" based on the process, but I need something to watch the process constantly.
If you need the app running all the time, then you don't need a Timer at all. Subscribe to the Process.Exited() event to be notified when it closes. For instance, with Notepad:
Public Class Form1
Private P As Process
Private FileName As String = "C:\Windows\Notepad.exe"
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim ps() As Process = Process.GetProcessesByName(Path.GetFileNameWithoutExtension(FileName))
If ps.Length = 0 Then
P = Process.Start(FileName)
P.EnableRaisingEvents = True
AddHandler P.Exited, AddressOf P_Exited
Else
P = ps(0)
P.EnableRaisingEvents = True
AddHandler P.Exited, AddressOf P_Exited
End If
End Sub
Private Sub P_Exited(sender As Object, e As EventArgs)
Console.WriteLine("App Exited # " & DateTime.Now)
Console.WriteLine("Restarting app: " & FileName)
P = Process.Start(FileName)
P.EnableRaisingEvents = True
AddHandler P.Exited, AddressOf P_Exited
End Sub
End Class
That would keep it open all the time, assuming you wanted to open it if it wasn't already running.
If you don't want to open it yourself, and need to detect when it does open, then you could use WMI via the ManagementEventWatcher as in this previous SO question.
I've done something similar to this to monitor an exe that I need to be running all the time, and to restart it if it was down.
Mine was running as a Windows Service - that way it would start when windows booted and id never need to look after it.
Alternatively you could just create it as a console app and put it in your startup folder?
I had:
Sub Main()
Do
Check_server()
Dim t As New TimeSpan(0, 15, 0)
Threading.Thread.Sleep(t)
Loop
End Sub
Public Sub Check_server()
Dim current_pros() As Process = get_pros()
Dim found As Boolean = False
If Now.Hour < "22" Then
For Each pro In current_pros
If pro.ProcessName.ToLower = "Lorraine" Then
found = True
Exit For
Else
found = False
End If
Next
If found Then
Console.WriteLine("Server up")
Else
Console.WriteLine("Server down - restarting")
restart_server()
End If
End If
End Sub
My "server" app was called Lorraine...Also a timer maybe better practice than having the thread sleep..
From my experience, a simple timer works best:
'Timer interval set to 1-5 seconds... no remotely significant CPU hit
Private Sub timerTest_Tick(sender As System.Object, e As System.EventArgs) Handles timerTest.Tick
Dim p() As Process = Process.GetProcessesByName("Skype")
lblStatus.Text = If(p.Length > 0, "Skype is running.", "Skype isn't running.")
End Sub
Your mileage may vary, but I don't like to deal with separate threads unless necessary.
So I made a small extraction program which just extracts a zip file to a location, and it also shows the progress of the extraction. But the problem is that whenever it's extracting large zips, the program kinda freezes while extracting and if you go off the process, you can't go back onto it until it's finished extracting, but you can still see the progressbar's progress. This is the code I have so far:
Form2.vb
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
If TextBox1.Text = "" Then
Else
ProgressBar1.Visible = True
Button2.Enabled = False
Button3.Enabled = False
TextBox1.Enabled = False
Unzip("FileToExtract.zip", "PathToExtractTo")
End If
End Sub
Unzip.vb
Imports Ionic.Zip
Module SimpleUnzip
Public Sub Unzip(ByVal ZipToUnpack As String, ByVal DirectoryToExstractTo As String)
Try
Using zip As ZipFile = ZipFile.Read(ZipToUnpack)
Form2.ProgressBar1.Maximum = zip.Entries.Count
Dim entry As ZipEntry
For Each entry In zip
entry.Extract(DirectoryToExstractTo, ExtractExistingFileAction.OverwriteSilently)
Form2.ProgressBar1.Value = Form2.ProgressBar1.Value + 1
Next
End Using
Catch ex1 As Exception
End Try
End Sub
End Module
So I have tried things like putting the SimpleUnzip sub in a background worker on the main forum and calling that, but that doesn't work at all, I have also tried a background worker on the module, it extracts but the progressbar doesn't work. Anyone know how to solve this problem?
As with any task using a BackgroundWorker, you do the work in the DoWork event handler and then you call ReportProgress to report the progress. This line:
Form2.ProgressBar1.Maximum = zip.Entries.Count
and this line:
Form2.ProgressBar1.Value = Form2.ProgressBar1.Value + 1
are going to have to be replaced with calls to ReportProgress. In the ProgressChanged event handler, you do what you normally would, i.e. update the ProgressBar.
I have a background process:
Public Shared Function CheckForInternetConnection() As Boolean
Try
Using client = New WebClient()
Using stream = client.OpenRead("http://www.google.com")
Return True
End Using
End Using
Catch
Return False
End Try
End Function
This process ties up my application, causing it to hang until completed.
I'm trying to incorporate this into a "BackGroundWorker" and tie it to a progress bar.
However, though the application no longer hangs when the process is running, the progress bar does.
I'd like the progress to progress from 0 - 50% during the above function, however currently, the progress bar sits at 0% until the process is complete, them jumps to 50%.
Private Sub bgwLongTask_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bgwLongTask.DoWork
For i As Integer = 1 To 10
' If we should stop, do so.
If (bgwLongTask.CancellationPending) Then
' Indicate that the task was canceled.
e.Cancel = True
Exit For
End If
CheckForInternetConnection()
LoadingScreen.CheckForIllegalCrossThreadCalls = False
Me.Text = i
' Notify the UI thread of our progress.
bgwLongTask.ReportProgress(i * 10)
Next i
End Sub
Private Sub bgwLongTask_ProgressChanged(ByVal sender As System.Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles bgwLongTask.ProgressChanged
If e.ProgressPercentage >= 0% And e.ProgressPercentage <= 50% Then
lblStatus.Text = "Checking Netowrk ..."
Else
lblStatus.Text = "Loading ..."
End If
prgPercentComplete.Value = e.ProgressPercentage
End Sub
By using:
LoadingScreen.CheckForIllegalCrossThreadCalls = False
Me.Text = i
I can see that the progress bar doesn't even start until CheckForInternetConnection() has completed.
Can someone please help me sort this out?
Much appreciated. Thanks.
You're looping from 1 to 10 calling CheckForInternetConnection() in every iteration, that doesn't makes sense. In this case the long-running process is CheckForInternetConnection() method, so you need to somehow "report progress" while the method is executing.
Possible way to notify user that the program is processing something when we can't calculate progress percentage of the process is using progress bar in indeterminate mode (by setting Style to Marquee in WinForm or setting IsIndeterminate to True in WPF).
I have been pulling my hair out trying to get this to work. If I step through the code in debugger it all works great.
My problem is if I just run it, only the last task responds. I'm guessing I am overwriting the background working or something. I am sure I am doing a few things wrong but my code is now messy as I tried many way while searching. I know of the threadpool and .Net 4.0 tasks but having a hard time getting to do what I need.
Basicly I am writing a program (trying more likely) that takes a list of computers and pings then, then checks their uptime and reports back.
This works fine in the UI thread (Obviously that locks up my screen). I can have the background worker just do this, but then it does each computer 1 by one, and while the screen is responsive it still takes a long time.
So my answer was to have a for loop for each server launching a new background worker thread. My solution does not work.
I have seen other threads that I could do it, but I need to use with events to call code to update to UI when each is done.
What is the most simple way to do this?
Here is my code. Most is just copy paste + modify till I get it working right.
So In the main class I have the testworker.
(I tried using Testworker() but it said I could not do that WithEvents)
When I click the button the list loads.
Private WithEvents TestWorker As System.ComponentModel.BackgroundWorker
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Button1.Click
Button1.IsEnabled = False
Dim indexMax As Integer
indexMax = DataGridStatus.Items.Count
For index = 1 To (indexMax)
Dim Temp As ServerInfo = DataGridStatus.Items(index - 1)
Temp.Index = index - 1
Call_Thread(Temp)
Next
End Sub
Private Sub Call_Thread(ByVal server As ServerInfo)
Dim localserver As ServerInfo = server
TestWorker = New System.ComponentModel.BackgroundWorker
TestWorker.WorkerReportsProgress = True
TestWorker.WorkerSupportsCancellation = True
TestWorker.RunWorkerAsync(localserver)
End Sub
Private Sub TestWorker_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles TestWorker.DoWork
Dim iparray As IPHostEntry
Dim ip() As IPAddress
Dim Server As ServerInfo
Server = e.Argument
Try
'Get IP Address first
iparray = Dns.GetHostEntry(Server.ServerName)
ip = iparray.AddressList
Server.IPAddress = ip(0).ToString
'Try Pinging
Server.PingResult = PingHost(Server.ServerName)
If Server.PingResult = "Success" Then
'If ping success, get uptime
Server.UpTime = GetUptime(Server.ServerName)
Else
Server.PingResult = "Failed"
End If
Catch ex As Exception
Server.PingResult = "Error"
End Try
TestWorker.ReportProgress(0, Server)
Thread.Sleep(1000)
End Sub
Private Sub TestWorker_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles TestWorker.ProgressChanged
Dim index As Integer
Dim serverchange As ServerInfo = DirectCast(e.UserState, ServerInfo)
index = DataGridStatus.Items.IndexOf(serverchange)
' index = serverchange.Index
DataGridStatus.Items.Item(index) = serverchange
' ProgressBar1.Value = e.ProgressPercentage
DataGridStatus.Items.Refresh()
End Sub
You are only getting the last result because you are blowing away your BackgroundWorker each time you call TestWorker = New System.ComponentModel.BackgroundWorker. Since the processing is being done asynchronously, this line is being called multiple times within your for loop before the previous work has finished.
Something like the following might work. (Sorry, my VB is rusty; there are probably more efficient ways of expressing this.)
Delegate Function PingDelegate(ByVal server As String) As String
Private _completedCount As Int32
Private ReadOnly _lockObject As New System.Object
Dim _rnd As New Random
Private _servers As List(Of String)
Private Sub GoButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles GoButton.Click
_servers = New List(Of System.String)(New String() {"adam", "betty", "clyde", "danny", "evan", "fred", "gertrude", "hank", "ice-t", "joshua"})
_completedCount = 0
ListBox1.Items.Clear()
GoButton.Enabled = False
BackgroundWorker1.RunWorkerAsync(_servers)
End Sub
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Dim servers As List(Of System.String) = DirectCast(e.Argument, List(Of System.String))
Dim waitHandles As New List(Of WaitHandle)
For Each server As System.String In servers
' Get a delegate for the ping operation. .Net will let you call it asynchronously
Dim d As New PingDelegate(AddressOf Ping)
' Start the ping operation async. When the ping is complete, it will automatically call PingIsDone
Dim ar As IAsyncResult = d.BeginInvoke(server, AddressOf PingIsDone, d)
' Add the IAsyncResult for this invocation to our collection.
waitHandles.Add(ar.AsyncWaitHandle)
Next
' Wait until everything is done. This will not block the UI thread because it is happening
' in the background. You could also use the overload that takes a timeout value and
' check to see if the user has requested cancellation, for example. Once all operations
' are complete, this method will exit scope and the BackgroundWorker1_RunWorkerCompleted
' will be called.
WaitHandle.WaitAll(waitHandles.ToArray())
End Sub
Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As System.Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
ListBox1.Items.Add(String.Format("{0} ({1}% done)", e.UserState, e.ProgressPercentage))
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As System.Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
GoButton.Enabled = True
End Sub
Private Function Ping(ByVal server As System.String) As System.String
' Simulate a ping with random result and duration
Threading.Thread.Sleep(_rnd.Next(1000, 4000))
Dim result As Int32 = _rnd.Next(0, 2)
If result = 0 Then
Return server & " is ok"
Else
Return server & " is down"
End If
End Function
Private Sub PingIsDone(ByVal ar As IAsyncResult)
' This method is called everytime a ping operation completes. Note that the order in which
' this method fires is completely independant of the order of the servers. The first server
' to respond calls this method first, etc. This keeps optimal performance.
Dim d As PingDelegate = DirectCast(ar.AsyncState, PingDelegate)
' Complete the operation and get the result.
Dim pingResult As String = d.EndInvoke(ar)
' To be safe, we put a lock around this so that _completedCount gets incremented atomically
' with the progress report. This may or may not be necessary in your application.
SyncLock (_lockObject)
_completedCount = _completedCount + 1
Dim percent As Int32 = _completedCount * 100 / _servers.Count
BackgroundWorker1.ReportProgress(percent, pingResult)
End SyncLock
End Sub
Update: I posted this answer focusing on exactly what you were trying to do from a technical standpoint (use many background workers) without really putting much thought into whether or not this was a good way to accomplish your real objective. In fact, I think you could achieve what you're going for much more easily with a single BackgroundWorker and something like a Parallel.ForEach loop in its DoWork event handler (this takes care of a lot of the nitty gritty work in, e.g., Dave's solution).
When you declare WithEvents TestWorker As BackgroundWorker in VB it wraps it up something like this (not exactly—this is just to illustrate the idea):
Private _TestWorker As BackgroundWorker
Private Property TestWorker As BackgroundWorker
Get
Return _TestWorker
End Get
Set(ByVal value As BackgroundWorker)
' This is all probably handled in a more thread-safe way, mind you. '
Dim prevWorker As BackgroundWorker = _TestWorker
If prevWorker IsNot Nothing Then
RemoveHandler prevWorker.DoWork, AddressOf TestWorker_DoWork
' etc. '
End If
If value IsNot Nothing Then
AddHandler value.DoWork, AddressOf TestWorker_DoWork
' etc. '
End If
_TestWorker = value
End Set
End Property
When you realize this, it becomes clear that by setting TestWorker to a new BackgroundWorker on every call to Call_Thread, you are removing any attached handlers from the object previously referenced by the field.
The most obvious fix would simply be to create a new local BackgroundWorker object in each call to Call_Thread, attach the handlers there (using AddHandler and RemoveHandler), and then just let it do its thing:
Private Sub Call_Thread(ByVal server As ServerInfo)
Dim localserver As ServerInfo = server
' Use a local variable for the new worker. '
' This takes the place of the Private WithEvents field. '
Dim worker As New System.ComponentModel.BackgroundWorker
' Set it up. '
With worker
.WorkerReportsProgress = True
.WorkerSupportsCancellation = True
End With
' Attach the handlers. '
AddHandler worker.DoWork, AddressOf TestWorker_DoWork
AddHandler worker.ProgressChanged, AdressOf TestWorker_ProgressChanged
' Do the work. '
worker.RunWorkerAsync(localserver)
End Sub
Creating the worker right there in the method should be fine as long as you do so from the UI thread, since BackgroundWorker automatically attaches to the current SynchronizationContext in its constructor (if I remember correctly).
Ideally you should use only 1 backgroundworker and use it like this:
Assemble all the work that needs to be done: in your case a list of ServerInfo
Do the work in the background: ping all the servers and keep the result
Report progress: for example after each server pinged
Put results back in DoWorkEventArgs.Result
Display the results back in your UI.
You need to attach TestWorker_DoWork and TestWorker_ProgressChanged to the DoWork and ProgressChanged events within Call_Thread. I haven't yet examined the rest of the code, but that is why it isn't doing anything now.
TestWorker = New System.ComponentModel.BackgroundWorker
TestWorker.WorkerReportsProgress = True
TestWorker.WorkerSupportsCancellation = True
AddHandler TestWorker.DoWork, AddressOf TestWorker_DoWork
AddHandler TestWorker.ProgressChanged, AddressOf TestWorker_ProgressChanged
TestWorker.RunWorkerAsync(localserver)