Async reading from process gets paused - vb.net

I have made an application in vb.net which launches a cmd like process in the background, reads its output asynchronously and redirects it in a textbox. Normally my application should show every new output line in the textbox in real time. Unfortunately here is what happens:
These are the first output lines which are shown in the textbox.
After a few seconds an unwanted pause occurs for some reason. No new lines are shown in the textbox even though there is output from the process. After a few minutes all that output i couldn't see because of the pause, shows up and then a pause occurs again.
Finally, the last bunch of lines show up and the process is terminated.
Here is my code:
Private Sub StartSteamCMD()
Try
Dim psi As ProcessStartInfo = New ProcessStartInfo("cmd.exe", "/C steamcmd +runscript " + temp + "Setti_SRCDS_Manager\install_update.txt")
With psi
.UseShellExecute = False
.RedirectStandardInput = True
.RedirectStandardOutput = True
.RedirectStandardError = True
.CreateNoWindow = True
.StandardOutputEncoding = Encoding.GetEncoding(CultureInfo.CurrentUICulture.TextInfo.OEMCodePage)
.StandardErrorEncoding = Encoding.GetEncoding(CultureInfo.CurrentUICulture.TextInfo.OEMCodePage)
End With
process = New Process With {.StartInfo = psi}
AddHandler process.OutputDataReceived, AddressOf Async_Data_Received
AddHandler process.ErrorDataReceived, AddressOf Async_Data_Received
process.Start()
process.BeginOutputReadLine()
process.BeginErrorReadLine()
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Sub
Private Sub Async_Data_Received(sender As Object, e As DataReceivedEventArgs)
Try
Invoke(New InvokeWithString(AddressOf Sync_Output), e.Data)
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Sub
Private Sub Sync_Output(text As String)
Try
TextBox1.AppendText(text + Environment.NewLine)
TextBox1.ScrollToCaret()
If Not String.IsNullOrEmpty(text) Then
If text.Contains("downloading") Or text.Contains("validating") Then
<code to animate the progress bar here>
ElseIf text.Contains("Success") Then
<the server is installed successfully, some code to run here>
ElseIf text.IndexOf("error", 0, StringComparison.CurrentCultureIgnoreCase) > -1 Then
<some code to run in case of error>
End If
End If
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Sub
Any thoughts of what can cause those pauses? I have been thinking the following. As you can see in the first screenshot, the last two lines are about establishing a connection with Steam servers (the place where the game server files will be downloaded from) and waiting for user info. Maybe that moment the process doesn't produce any output for a few seconds. So at some point during those seconds, the first pause of async reading occurs. Then the process starts producing output again but it's not redirected to the textbox because of the pause. Could it be the async reading gets paused because of the process not producing any output for a few seconds?
I don't know what else to think. I'm tired of searching the internet for a solution, because i can't find any post about my problem or at least something similar. I hope you guys can help me out. Thanks in advance

Related

Cannot cancel a background worker running a SQL query

I have searched SO and Google but I cannot find the information I am seeking which may indicate I am not using Background Workers correctly. In my case, I am running a SQL query in a Background Worker so the GUI does not freeze for the user. Please note, supportscancellation is set to true.
However, I am trying to stop the background worker if the user wants to cancel the query. Because my background worker does not have a loop, I am unsure how to accomplish this.
My background worker simply looks at a boolean to determine the type of SQL query, then makes a call to another event to run it.
Sub BackgroundWorker1DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs)
If Psearch = True Then
P_Search()
End If
If Asearch = True Then
A_Search()
End If
If Msearch = True Then
M_Search()
End If
If Dsearch = True Then
D_Search()
End If
End Sub
Based on articles I find, it seems adding an if statement to check for cancellationpending is irrelevant as it would only fire the one time (at the start of the RunWorkerAsync). So, I have tried using a For/Do loop to check for cancellationpending or workercompleted but it still will not cancel and dispose of the background worker. I have validated this by attempting to restart the background worker but I am erroring out due to workser isbusy.
Sub Button7Click(sender As Object, e As EventArgs)
If backgroundWorker1.IsBusy() Then
backgroundWorker1.CancelAsync()
backgroundWorker1.Dispose()
End If
End Sub
Sub D_Search()
Dim sqlConnection1 As New SqlConnection(GlobalConString)
Dim cmd As New SqlCommand
cmd.CommandText = RealQuery
cmd.CommandTimeout = 0
cmd.Connection = sqlConnection1
sqlConnection1.Open()
Dim daQuery As New System.Data.SqlClient.SqlDataAdapter(cmd)
Dim dsQuery As New DataSet
Try
If backgroundWorker1.CancellationPending = False
daQuery = New System.Data.SqlClient.SqlDataAdapter
daQuery.SelectCommand = cmd
daQuery.Fill(dsQuery, "Query")
tblQuery = dsQuery.Tables("Query")
Else
backgroundWorker1.Dispose()
sqlConnection1.Close()
End If
Catch Err As System.Exception
MessageBox.Show(Err.ToString)
Finally
sqlConnection1.Close()
End Try
End Sub

Timers and variables in vb.net

I have a class with a timer. The timer starts in the new method. The method associated with the timer is checkMatch() but checkTimer() ensures the parameters are met to start the timer. They are met and the timer starts. The method checkMatch() is currently rewritten to only execute once since I know it keeps repeating itself. The only place to change the boolean variable oneTime is located inside of checkMatch(). I included the autoLoad() function just in case it is needed. Whenever I launch this class, the forms associated with the other methods not listed will pop up and instantly go away. The console shows that checkMatch() is being called regularly (which it should), but that the variable oneTime remains True each time it is called. The timer is started at the end of the autoLoad() method. I know all the methods in autoLoad() are being called and executed.
Since oneTime is showing as True constantly in the console, I am assuming I have a conception issue somewhere. Why is oneTime true each time checkMatch() is called?
' a user is required for this class
Public Sub New(ByVal my_user As RGOUser)
My.Settings.selected_summoner = "huge legend"
My.Settings.Region = "na1"
My.Settings.Save()
myUser = my_user
AddHandler tmr.Elapsed, AddressOf checkMatch
checkTimer()
End Sub
Public Sub checkMatch()
Console.WriteLine(Me.GetType.ToString() + "||" + System.Reflection.MethodInfo.GetCurrentMethod.ToString())
Console.WriteLine(oneTime)
Try
Dim psList() As Process = Process.GetProcesses()
For Each p As Process In psList
' If strLeagueProcessName = p.ProcessName) And Not boolInGame Then
' remove later and replace with commented line
If oneTime Then
Console.WriteLine("checkMatch Success.")
boolInGame = True
oneTime = False
tmr.Interval = timerInsideMatch
tmr.Stop()
Try
autoLoad()
Catch ex As Exception
End Try
Return
Else
Return
End If
Next
Catch ex As Exception
Console.WriteLine("checkMatch Failure. " + ex.Message)
End Try
boolInGame = False
tmr.Interval = timerOutsideMatch
End Sub
Private Sub autoLoad()
Console.WriteLine(Me.GetType.ToString() + "||" + System.Reflection.MethodInfo.GetCurrentMethod.ToString())
If searchMatch() Then
Dim x As New System.Threading.Thread(AddressOf loadConfiguration)
Dim y As New System.Threading.Thread(AddressOf loadMatch)
Dim z As New System.Threading.Thread(AddressOf launchMinimap)
If My.Settings.configuration_auto_load Then
Try
x.Start()
Catch ex As Exception
End Try
End If
If My.Settings.loadMatch Then
Try
y.Start()
Catch ex As Exception
End Try
End If
If My.Settings.launchMinimap Then
Try
z.Start()
Catch ex As Exception
End Try
End If
x.Join()
y.Join()
z.Join()
tmr.Start()
End If
End Sub

Destroy Thread with 'aborted' state

Using background worker in a winform program. Also communicating with somedevices
I have a stop button which tries to stop the background worker thread, which works, but sometimes, the background worker thread remains in state "Aborted"
I have to mention that I take care about the exception that rise, and also use a 'Finally' block to stop the communication with the devices
I need to stop the thread immediately, something like an emergency button...
Some code:
Private Sub BtnStopTest_Click(sender As Object, e As EventArgs) Handles btnStopTest.Click
Try
stoppedTesting = True
Log("Stopping operations safely. (You might have to wait some time )", Color.Blue, New Font("Microsoft Sans Serif", 9, FontStyle.Bold))
If bgWorkThread IsNot Nothing Then
'stop thread
'if thread is sleeping (waiting for a time)
If bgWorkThread.ThreadState = ThreadState.Background + ThreadState.WaitSleepJoin Then
bgWorkThread.Interrupt()
Else 'if thread is working normally
bgWorker.CancelAsync()
tEO.DoWorkEventArgs.cancel = True
bgWorkThread.Abort()
'sometimes, here the Thread has state 'Aborted
End If
ElseIf bgWorkThread Is Nothing Then
Dim ee As New System.ComponentModel.RunWorkerCompletedEventArgs(New Object, Nothing, False)
BgWorker_RunWorkerCompleted(New Object, ee)
End If
Catch ex As Exception
Utils.PreserveStackTrace(ex)
Log("Error when stopping testing" & vbCrLf & Utils.ReadException(ex), MessageType.ErrorMessage)
End Try
End Sub
Private Sub BgWorker_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles bgWorker.DoWork
Try
'some other things to do
For Each testStep In stepList
Try
'main operations and communication with device
' below functions are all different
'something like:
'CommunicationWithDevice1()
'CommunicationWithDevice2()
'CommunicationWithDevice3()
'CommunicationWithDevice4()
'....
'CommunicationWithDevice20()
Catch ex As Exception When TypeOf ex Is ThreadAbortException OrElse TypeOf ex Is ThreadInterruptedException
Utils.PreserveStackTrace(ex)
Log("Exception in thread" & vbCrLf & Utils.ReadException(ex), MessageType.ErrorMessage)
e.Cancel = True
If ex.GetType = GetType(ThreadAbortException) Then Thread.ResetAbort()
If stoppedTesting Then Exit For
Catch ex As Exception
If stoppedTesting Then Exit For
End Try
Catch ex As Exception When TypeOf ex Is ThreadAbortException OrElse TypeOf ex Is ThreadInterruptedException
e.Cancel = True
Log("Background worker thread was interrupted!")
Log("Background worker thread was interrupted!", Color.Red, New Font("Microsoft Sans Serif", 9, FontStyle.Bold))
Catch ex As Exception
Utils.PreserveStackTrace(ex)
Log("Error when doing background work!" & vbCrLf & Utils.ReadException(ex), Color.Red, New Font("Microsoft Sans Serif", 9, FontStyle.Bold))
Finally
StopCommunication()
End Try
End Sub
What can I do to completely destroy the thread?
If there is not possibility, any workaround to exit my 'DoWork' method immediately?
BackgroundWorker.CancelAsync() does not cancel the thread immediately, it actually posts the STOP message on the queue as you can check the BackgroundWorker.isCancellationPending is set to true, if you want to cancel immediately, you could have a global boolean and set it if you can to cancel the thread. In your Do_Work() method, you can regularly check the boolean and if it is set, run Exit Sub.
Something like this should do
Else 'if thread is working normally
cancelThread = true
tEO.DoWorkEventArgs.cancel = True
and inside your Do_Work sub
For Each testStep In stepList
Try
If cancelThread Then
Exit Sub
This will cleanly stop the BackgroundWorker and allow you to run the RunWorkerCompleted routine. Inside it, you can check for the cancelThread to know if the BackgroundWorker ran successfully or it was aborted.
UPDATE 2
After going through your code one more time, why do you need to use BackgroundWorker and Thread together? If you want immediate cancellation, use only Thread. Also, you only need one TRY..Catch for the entire function, that way it will ensure that the thread exits. You can always assign the thread to nothing and Garbage Collector will take care of freeing resources for you(If you are concerned with ABORTED)
Dim myThread As Thread = new Thread(new ThreadStart(yourFunctionForCommunication))
myThread.Start()
And in your function
Try
.. Do your work
Catch Ex As ThreadAbortException
Exit Sub;
Finally
//Do your cleanup code here
End Try
The abort call
myThread.Abort()
While mythread.ThreadState <> ThreadState.Aborted
End While
myThread = Nothing
But I should warn you that Aborting threads is not a safe process. See this MSDN Post.
If you would still want to use Background worker, i would suggest you to look into List<dynamic>. Add all the Communicatewithdevice1()....to 20() there then use that in a loop, that way you would not have to write the If conditional 20 times. See the post Here

How to avoid Thread.Abort() in this case?

I know that Thread.Abort() is not really safe, but I cannot imagine what it could cause in case below:
Private threadLoadList As Thread
Private Sub LoadList(ByVal argument As Integer)
Try
Refresh_Grid(GET_DATA(argument), argument) 'Fills grid with data loaded from database
Change_Cursor() 'Changes cursor to Cursor.Default
Catch ex As ThreadAbortException
'Do nothing
Catch ex As Exception
Error_log(ex.Message) ' Saves ex.Message in database
End Try
End Sub
Private Sub SomeValueChanged(sender As Object, e As EventArgs) Handles Control.ValueChanged
If Not threadLoadList Is Nothing Then
If threadLoadList.IsAlive Then
threadLoadList.Abort()
End If
End If
Cursor = Cursors.WaitCursor
threadLoadList = New Thread(AddressOf LoadList)
threadLoadList.Start(1)
End Sub
As you can see, user can change some value (ComboBox) and in result change content of the grid. Function GET_DATA() takes around 10 seconds, thus it is possible that user changes value of Combobox before grid is refreshed - that is why previously started thread is killed.
Is it dangerous? If yes, could you propose some other solution?
Ok, I understand ;). I try to avoid timeouts (in some cases query executes below 1 second) Is it better solution:
Private threadLoadList As Thread
Private Changed As Boolean = False
Private Sub LoadList(ByVal argument As Integer)
Try
Dim dt As DataTable = GET_DATA(argument)
'Enter Monitor
If Changed Then
Return
End IF
'Exit Monitor
Refresh_Grid(dt, argument) 'Fills grid with data loaded from database
Change_Cursor() 'Changes cursor to Cursor.Default
'Enter Monitor
Changed = False
'Exit Monitor
Catch ex As ThreadAbortException
'Do nothing
Catch ex As Exception
Error_log(ex.Message) ' Saves ex.Message in database
End Try
End Sub
Private Sub SomeValueChanged(sender As Object, e As EventArgs) Handles Control.ValueChanged
'Enter Monitor
Changed = True
'Exit Monitor
Cursor = Cursors.WaitCursor
Dim t As Thread = New Thread(AddressOf LoadList)
t.Start(1)
End Sub
First things first, unless there is extra code in Refresh_Grid that I am not seeing it looks like you are attempting to modify a UI element from a thread other than the UI thread. That is a big no-no. UI elements are designed to be accessed only from the UI thread so attempting to modify one on a worker thread will lead to unpredictable behavior.
In regards to your usage of Thread.Abort the answer is yes; it is dangerous. Well, it is dangerous in the sense that your application will throw an exception, corrupt data, crash, tear a hole in spacetime, etc. There are all kinds of failure modes that might be expected from aborting a thread. The best solution is to use a cooperative termination protocol instead of doing a hard abort. Refer to my answer here for a brief introduction on valid approaches.

Is it correct to raise custom events from Timer/ BackgroundWorker Completed that go on and update UI

My winforms application needs to interact with hardware devices.
The display is kind of workflow... after step 1 .. completed.. go to step 2 .. etc.
The display is dynamic with controls being made visible at run time and activity being initiated (each of these User controls use timer/ BGWorker within).
I m Raising custom events from timer/ BGWorker_Completed. this will help proceed to the next step and update UI. Am I doing the right thing?
Im a newbie in winforms and Im just not able to figure out why the display is failing.
Im not catching any exception... but after a particular step I do not see the controls !!! I do not know where and how to debug this scenario. If I execute the main form as a stand alone.. I can see the display as well.
however if I navigate from the login page/ change the tabs within the main form and return back.. I do not see the display.
I tried putting in checks before calling the update on UI and in the same order as below.
Thread.Current.IsBackground returns false, control.IsHandleCreated returns true or else Im creating it with dim x=Control.handle()) Me.InvokeRequired/ Control.invokeRequired returns false (the way I wanted). However I do not see userControl being displayed... visibility/color/ everything is being set (in the program).. and Im able to see the hardware interaction!!!.. But no display :-( (step 4 and later)
Im not doing anything fancy in the login page...or in tabChanged events.
(On tab changed event... Im only cleaning up open connections/ closing bg workers if any.. which would be connected back whenever required)
Please let me know if I need to do anything...and how to solve this problem.
I m also calling EnsureHandleIsCreated(control) subroutine soon after initialize component of every user control/ main form.
'Code in Login Form
Dim myForm as new MainForm()
myForm.ShowDialog(Me) ' here i also tried with show/showDialog.. with/without ownerForm
Me.Hide() ' Hide login page
'Code for checking if handle is created or not
Public Sub CheckForInvalidThread()
frmMain.CheckForIllegalCrossThreadCalls() = True
If Thread.CurrentThread.IsBackground Or Not Thread.CurrentThread.Name Is THREAD_MAIN_NAME Then
Throw New InvalidOperationException(THREAD_IS_INVALID)
End If
If Not Me.IsHandleCreated Then
Dim x = Me.Handle()
Thread.Sleep(20)
End If
End Sub
Public Sub EnsureHandleIsCreated(ByRef c As Control)
Try
If Not c.IsHandleCreated Then
Dim h As System.IntPtr = c.Handle()
End If
If c.HasChildren Then
For Each child As Control In c.Controls
Try
EnsureHandleIsCreated(child)
Catch ex As Exception
DAL.LogException(ex.Message, ex.StackTrace, "EnsureHandleIsCreated: " & c.Name, 0)
End Try
Next
End If
Catch ex As Exception
DAL.LogException(ex.Message, ex.StackTrace, "EnsureHandleIsCreated: " & c.Name, 0)
End Try
End Sub
Private Sub frmMain_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
lbRole.Text = RoleName
lbName.Text = UserName
Try
Me.Activate()
If Thread.CurrentThread.IsBackground Then
Throw New ApplicationException(THREAD_IS_INVALID)
End If
Thread.CurrentThread.Name = THREAD_MAIN_NAME
CheckForInvalidThread()
clGlobals.frmMain = Me
If RoleName Is Nothing Or String.IsNullOrEmpty(RoleName) Or RoleName.Equals("OPERATOR", StringComparison.InvariantCultureIgnoreCase) Then
tcEtch.TabPages("tbMaintenance").Hide()
tcEtch.TabPages("tbAdmin").Hide()
End If
Catch ex As Exception
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
Exit Sub
Finally
End Try
Initiate()
End Sub
Public Sub GoToNextStep() Handles Me.GoToNextStepEvent
Try
CheckForInvalidThread()
CurrentStep = CurrentStep + 1
Select Case CurrentStep
Case 0 To 2
If Me.InvokeRequired Then
_delegateDisplayInitiate = AddressOf DisplayStep2
Me.Invoke(_delegateDisplayInitiate)
Else
DisplayStep2()
End If
Case 3
If ucCycleStart.InvokeRequired Then
_delegateDisplayInitiate = AddressOf DisplayStep3
ucCycleStart.Invoke(_delegateDisplayInitiate)
Else
DisplayStep3()
End If
Case 4
If Me.InvokeRequired Or ucPartCountVerification.InvokeRequired Or Thread.CurrentThread.IsBackground Then
Throw New Exception("Check out")
End If
EnsureHandleIsCreated(ucPartCountVerification)
If ucPartCountVerification.InvokeRequired Then
_delegateDisplayInitiate = AddressOf DisplayStep4
ucPartCountVerification.Invoke(_delegateDisplayInitiate)
Else
DisplayStep4()
End If
End Select
Catch ex As Exception
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
End Try
End Sub
Private Sub DisplayStep4() Handles Me.DisplayStep4Event
ucPartCountVerification.Visible = True
ucPartCountVerification.Show()
ucPartCountVerification.Initiate()
End Sub
Public Sub Initiate()
frmMain.CheckForInvalidThread()
'Just to verify if things are fine.. i put in this check below
If Me.InvokeRequired Or pnStep4.InvokeRequired Or Not (Me.IsHandleCreated And pnStep4.IsHandleCreated ) Then
MessageBox.Show("cHECK OUT")
Else
Me.Visible = True
pnStep4.Visible = True
Me.BackColor = Color.Red
pnStep4.BackColor = Color.Gray
Dim height = Me.Size.Height
Dim width = Me.Size.Width
MessageBox.Show(height.ToString() + Me.InvokeRequired.ToString())
End If
end Sub
You certainly can raise events from a BackgroundWorker or Timer. You're already checking InvokeRequired, which is the correct thing to do. You need to then call BeginInvoke (or Invoke, if it needs to be synchronous) to update your UI.
You are on the right track. When dealing with the events that come from some libraries, you cannot be certain on which thread they are delivered. That is disappointing because Windows Forms has the restriction that call calls be made from the GUI thread. This MSDN article explains the problem.
You are on the right track with InvokeRequired. It lets you see whether you are on the right thread, but you need a way to handle this case and re-invkoe the event on the proper thread.
Here is how I do this in C#...
public delegate void uiEventHandler();
void uiEvent(object sender, EventArgs e)
{
if (InvokeRequired)
{
// We are not on the correct thread. We'd better get there.
var eh = new uiEventHandler(uiEvent);
Invoke(eh, new object[] { sender, e });
return; //This thread has no more work to do.
}
// Do your work here that requires being performed on the GUI thread.
}