Abort running code on form when form is disposed? - vb.net

I have a large, complex client application I am now maintaining. For security purposes, it requires a constant connection to the server. It has a thread that is handling all the socket communication between itself and the server.
The way it's currently written, if there is any communication issue with the server, the communications thread fires off an event that closes and disposes all open forms and returns the user back to the initial connection/logon screen.
The problem I'm having is that sometimes this communication issue can happen in the middle of the execution of a function (such as one that is blocked by a modal form). When the modal form and parent form are disposed of, the function still finishes running, often leading to exceptions and errors.
For example, the report form has a function that opens a dialog, accepts input, and then runs a report based on that input:
'Inside the class for the ReportForm:
Private Sub RunReport()
'Run code that requests list of reports from server
_ReportSelectionForm = New frmReportSelection(reportList)
_ReportSelectionForm.ShowInTaskbar = False
Me.AddOwnedForm(_ReportSelectionForm)
_ReportSelectionForm.ShowDialog(Me)
'the following code will still execute when ReportForm (Me) is disposed:
username = _ReportSelectionForm.txtUsername
If (_ReportSelectionForm.DialogResult = Windows.Forms.DialogResult.Ok) Then
'Run code
ElseIf (_ReportSelectionForm.DialogResult = Windows.Forms.DialogResult.Cancel) Then
'Run different code
End If
'etc
End Sub
So, if the Report Selection Form is open and the communications thread times out communications with the server, the communications error event fires which closes and disposes the ReportForm. This, in turn, closes the _ReportSelectionForm Dialog. When this happens, even though the parent form has been disposed, finishes running the code after "_ReportSelectionForm.ShowDialog(Me)". This throws an exception on "_ReportSelectionForm.DialogResult" or "_ReportSelectionForm.txtUsername" because _ReportSelectionForm is Nothing.
If this was one isolated place, I could handle this with a few extra checks before continuing to run the function, but it's all over this large program.
What's the best way to handle this? Can I abort code execution on a form that I'm closing?
Hopefully I explained it adequately. My Google-Fu is failing me. Thanks in advance.

Change your code to:
Dim result as DialogResult = _ReportSelectionForm.ShowDialog(Me)
If (result = Windows.Forms.DialogResult.Ok) Then
'Run code
ElseIf (result = Windows.Forms.DialogResult.Cancel) Then
'Run different code
End If
That way you're not referencing the ReportSelectionForm.

Related

Winform controls loaded from CefSharp causing Cross-Thread errors

I'm integrating CefSharp into a legacy Winforms application. Currently the application uses the default .NET browser control. Unfortunately, this control has a memory leak which is causing serious problems. I'm trying to get the CefSharp browser integrated without major refactoring, since the application is scheduled to be retired later this year and replaced with a new WPF application.
So far, I'm able to make CefSharp work in most scenarios. Standard webpages open without a problem. However, some of these pages have specially formed links which, when interpretted by the application, open up .Net forms instead of other webpages. This is where I run into an issue. When the webpage opened in CefSharp calls one of these links and the application attempts to open the new form, it appears to be doing so on the thread hosting the CefSharp instance, which is different from that hosting the application itself. This leads to numerous cross-thread issues (the legacy application in question isn't particularly well architectured). I'm trying to find a way to solve this issue without rearchitecturing the Winform application.
The following is a brief example of the situation.
Controls
frmMain
This is the primary form on the application. It has a number of duties, but the one pertinent to the current situation is that it hosts a Telerik DocumentTabStrip which contains the application's "tabs" (each browser or form opens within one of these tabs). It also contains the method that is used to load the various form or browser controls that are instantiated and add them to the aforementioned DocumentTabStrip.
ucChromeBrowser
This is the object which wraps the CefSharp browser instances which get created. It also has all the Event Handlers for the CefSharp events, such as IRequestHandler, ILifespanHandler, IContextMenuHandler, etc.
EntryScreens.uc_Foo
This is one of the Winform controls that are called from the webpage hosted in ucChromeBrowser. A typical link looks like WEB2WIN:Entryscreens.uc_Foo?AdditionalParameterDataHere. We intercept these calls in frmMain and instead of attempting to open the link in a browser, we instantiate a new instance of EntryScreen.uc_Foo and load that new form into frmMain.DocumentTabStrip as follows.
Dim _DockWindow As Telerik.WinControls.UI.Docking.DocumentWindow = Nothing
Dim _Control As ucBaseControl = Nothing
Dim _WebBrowser As ucBrowser = Nothing
Dim _isWebBrowerLink As Boolean = False
'Do Other Stuff here, such as instantiating the _Control or _WebBrowser instance, setting _isWebBrowserLink, etc.
_DockWindow = New Telerik.WinControls.UI.Docking.DocumentWindow
If _isWebBrowerLink Then
If Not IsNothing(_WebBrowser) Then
_WebBrowser.Dock = DockStyle.Fill
_DockWindow.Controls.Add(_WebBrowser)
End If
Else
_Control.Dock = DockStyle.Fill
_DockWindow.Controls.Add(_Control)
End If
DocumentTabStrip.InvokeIfRequired(Sub() DocumentTabStrip.Controls.Add(_DockWindow))
(In case it matters, here's the InvokeIfRequired method I'm calling.)
Public Module ISynchronizeInvokeExtensions
<Runtime.CompilerServices.Extension>
Public Sub InvokeIfRequired(obj As ISynchronizeInvoke, action As MethodInvoker)
Dim idleCounter As Integer = 0
While Not CType(obj, Control).Visible
'Attempt to sleep since there's no visible control
If idleCounter < 5 Then
Threading.Thread.Sleep(50)
Else
Exit While
End If
End While
If obj.InvokeRequired Then
Dim args = New Object(-1) {}
obj.Invoke(action, args)
Else
action()
End If
End Sub
End Module
The issue comes up when trying to call DocumentTabStrip.InvokeIfRequired(Sub() DocumentTabStrip.Controls.Add(_DockWindow)). From what I can tell, it appears that this changes the layout of the controls hosted within, causing various controls to be instantiated or to have their events called. This, in turn, causes an InvalidOperationException (e.g.: "Cross-thread operation not valid: Control 'pnlLoading' accessed from a thread other than the thread it was created on."). The specific child control varies from Form to Form (so for Form A it might always be pnlLoading which causes it, but for another Form it might be a different control). But most or all of them exhibit this behavior. I have no doubt it's due to the controls themselves being poorly designed, but I don't really have the time to refactor all of them.
So that's the situation. It appears as though the multi-threaded nature of CefSharp is conflicting with the single-threaded nature of the controls in question and causing them to get called on a different thread than they would be otherwise. Is there a way to prevent this?
Controls should only be created on the UI thread, and not in background threads. The error message tells you pretty clearly what's going on.
Cross-thread operation not valid: Control 'pnlLoading' accessed from a thread other than the thread it was created on.
You are accessing the control from the UI thread but due to that it is actually created in a background thread you are performing a cross-thread access since you're invoking to the wrong thread.
Whatever you do you would almost always have to invoke to the background thread when accessing the control, but you cannot do this for any automatic access done by, for instance, the UI message loop.
Therefore you should put all control creation and accessing in the UI only, thus meaning you must put all that code in your first code block in an invoke.
DocumentTabStrip.InvokeIfRequired( _
Sub()
Dim _DockWindow As Telerik.WinControls.UI.Docking.DocumentWindow = Nothing
Dim _Control As ucBaseControl = Nothing
Dim _WebBrowser As ucBrowser = Nothing
Dim _isWebBrowerLink As Boolean = False
'Do Other Stuff here, such as instantiating the _Control or _WebBrowser instance, setting _isWebBrowserLink, etc.
_DockWindow = New Telerik.WinControls.UI.Docking.DocumentWindow
If _isWebBrowerLink Then
If Not IsNothing(_WebBrowser) Then
_WebBrowser.Dock = DockStyle.Fill
_DockWindow.Controls.Add(_WebBrowser)
End If
Else
_Control.Dock = DockStyle.Fill
_DockWindow.Controls.Add(_Control)
End If
DocumentTabStrip.Controls.Add(_DockWindow)
End Sub)

Difference between form.close and application.exit

I am recently out of school, working my first job as a programmer. We have a user-reported bug that is occurring when our application timeout timer closes the application. I'm pretty sure I've narrowed it down, but am curious as to why the original programmer would have done this, if it's good coding practice, and if so, I am curious if anyone has a way to handle this. We are getting a post-closing system error, as it occurs after the main form closes, so we don't get any exception log input.
The close functionality of the timeoutTimer_tick handler does the following:
For iCount As Int16 = Application.OpenForms.Count - 1 To 0 Step -1
Try
Application.OpenForms(iCount).Close()
Catch
End Try
Next
Try
Application.Exit()
Catch ex As Exception
End Try
The program is set with the application property to close when the main form closes (not ALL open forms are closed). This makes me wonder why we're looping through each form and closing them individually, and then calling Application.Exit()
I'm pretty sure our error is because of the Application.Exit call after all open forms are closed. It doesn't see the main form, as it was closed during the loop, and throws an error. I feel like we should be using one or the other, but not both.
Any input or advice? Which is better, or are either better (or should this code work without error, and I am simply wrong).
Thanks
In WinForms you have a Shutdown Mode setting (Project Properties>Application Tab)
This allows you to specify When startup form closes or When last form closes
So logically you should not need Application.Exit .If you do then there is some other object hanging around in memory that you need to dispose of (something started on a thread / background worker etc)
So If you have the startup form setting:
[StartupFormName].Close
or if you have the last form setting:
Do While My.Application.OpenForms.Count > 0
My.Application.OpenForms(0).Close()
Loop

Does DoEvents effect only the current thread?

I have a simple 'Working' form that runs on its own thread to keep the user informed that the application hasn't died during long running operations. In order to get the working form to update I had to insert a DoEvents() call.
I'm curious, will this only pump messages for the current thread I'm in, or will it do it for the whole application? I would prefer that the main window stay unresponsive till the operation finishes, so I'm curious as to the behavior. Below is the code for the working form.
Just to be clear, I'm fine with the code I have, but I would like to know how DoEvents() behaves with threads.
Public Class frmWorking
''' <summary>
''' Creates and starts a new thread to handle the Working Dialog
''' </summary>
''' <returns>The thread of the Working dialog.</returns>
''' <remarks></remarks>
Public Shared Function StartWait() As WorkingFromToken
Dim th As New Threading.Thread(AddressOf ShowWait)
Dim token As New WorkingFromToken
th.Start(token)
Return token
End Function
Private Shared Sub ShowWait(token As WorkingFromToken)
Dim frm As New frmWorking
Try
frm.Show()
Do
If frm.txtWait.Text.Length > 45 Then
frm.txtWait.Text = "Working"
Else
frm.txtWait.Text &= "."
End If
Windows.Forms.Application.DoEvents()
Threading.Thread.Sleep(250)
Loop While token.Running
frm.Hide()
Catch ex As Threading.ThreadAbortException
Threading.Thread.ResetAbort()
frm.Hide()
Return
End Try
End Sub
End Class
DoEvents will only pump the current UI thread.
However, I do not recommend your approach.
Instead, you should do your work on a background thread, and show a modal progress form on the UI thread and update it using BeginInvoke or a BackgroundWorker.
DoEvents will only effect the thread from which it is called. It will dequeue all windows messages posted to that thread and dispatch them accordingly. After all messages have been dispatched it will return back to the caller.
I have a couple of other observations about your code though.
You have basically created your own crippled version of a message loop by calling DoEvents repeatedly in a loop. It would be better to just call Application.Run to initiate a full blown message loop.
Creating a message loop on a thread other than the main UI thread is rarely a good idea. There are some weird things that occur that are hard to deal with. For example, a modal dialog box from one thread could overlap a modal dialog box from another.
Attempting to catch a ThreadAbortException is pointless in most situation. If you ever get this exception then it is possible (perhaps even likely) that the state of the entire AppDomain has been corrupted. It is better to tear down the application domain than to try to gracefully deal with it. This is because the exception could be injected at any point during the execution of the thread and those injection points could be in the middle or a write, a lengthy operation, or otherwise some unsafe point.
As a corollary to the point above do not use Thread.Abort to terminate another thread. There are many too many things that can go wrong. It is better to cause the thread to end gracefully using safer mechanisms.

Update UI form from worker thread

I am new to multi-threading in VB.NET and have come across a problem whereby I am wanting to append text to a text box on a form from a service thread running in the background.
The application I am developing is a client/server listener, I have been able to get the client and server PC's to talk with each other (confirmed through MsgBox), however I am now struggling to get the service thread on the server to append the text to the textbox, nothing vissible occurs.
I have a form named testDebug which calls a class (RemoteSupport), this class does all the handshake tasks and updates the textbox with the connection data.
Can anyone identify where I am going wrong and point me in the right direction?
The following is the code I have:
The form has a textbox named txtOutput, the following is from the remoteSupport class
Dim outMessage As String = (encoder.GetString(message, 0, bytesRead))
MsgBox(outMessage, MsgBoxStyle.Information, "MEssage Received")
If outMessage IsNot Nothing Then
If testDebug.InvokeRequired Then
' have the UI thread call this method for us
testDebug.Invoke(New UpdateUIDelegate(AddressOf HandleClientComm), New Object() {outMessage}) '
Else
testDebug.txtOutput.AppendText(outMessage)
End If
'RaiseEvent MessageReceived(outMessage) // a previous attempt to use custom events
End If
I am not sure if the invoke method is the ideal solution or if custom events are, I have spent some time on trying to get custom events to work, but these didnt work either.
// In the RemoteSupport class
Public Delegate Sub MessageReceivedHandler(ByVal message As String)
Public Shared Event MessageReceived As MessageReceivedHandler
// Located throughout the RemoteSupport class where debug information is required.
RaiseEvent MessageReceived(outMessage)
// Located in the code-behind of the form
Private Sub Message_Received(ByVal message As String)
testDebugOutput(message) // this is a function I have created
// to append the text to the text box
End Sub
The code supplied has been cut down so if there is anything else that you want to see or any questions please let me know.
Thanks for your assistance.
EDIT: I have uploaded the two VB files (form and class) to my site, I would appreciate it if someone could have a look at it to help me with identifying the problem with the UI not updating.
I have tried a few other things but nothing seems to be updating the UI once the worker thread has started.
Form: mulholland.it/testDebug.vb.txt
Class: mulholland.it/remoteSupport.vb.txt
Thanks for your assistance.
Matt
I have a form named testDebug...
If testDebug.InvokeRequired Then
This is a classic trap in VB.NET programming. Set a breakpoint on the If statement. Notice how it returns False, even though you know that the code is running on another thread?
InvokeRequired is an instance property of a Form. But testDebug is a class name, not a reference to an instance of a form of type testDebug. That this is possible in VB.NET has gotten a lot of VB.NET programmers in deep trouble. It is an anachronism carried over from VB6. It completely falls apart and blows up in your face when you do this in a thread. You'll get a new instance of the form, instead of the one that the user is looking at. One that isn't visible because its Show() was never called. And otherwise dead as a doornail since the thread isn't running a message loop.
I answered this question several times already, with the recommended fix. I'll just refer you to them rather than rehashing it here:
Form is not updating, after custom class event is fired
Accessing controls between forms
The Delegate method is likely the way you want to go, but I don't see the declaration of the UpdateUIDelegate anywhere
I believe your code should look something like this (assuming you have a reference to the testdebug form local to your remotesupport class
Dim outMessage As String = (encoder.GetString(message, 0, bytesRead))
MsgBox(outMessage, MsgBoxStyle.Information, "MEssage Received")
If outMessage IsNot Nothing Then
If testDebug.InvokeRequired Then
' have the UI thread call this method for us
testDebug.Invoke(New MessageReceivedHandler(AddressOf Testdebug.Message_Received), New Object() {outMessage})
Else
testDebug.txtOutput.AppendText(outMessage)
End If
end if

Can not get Process to die in VB.net

I've inherited some VB.net code. My task is to find out why it isn't working. I have 2 applications. The first one
is run as a service, infinitely checking a table to see if there are any tasks to be handled. If it finds one, its supposed to fire off the second application to handle the task then returns to the loop and checks for another. Both these applications are forms but they do not show any windows. The problem I'm having is after the second application is finished, the first application never gets a signal it is done so it is waiting forever, thus it can't move onto the next task. If I go into TaskManager and kill the second application, the first one gets that notification and proceeds as it should. Below is how I am creating the process and waiting for it. I've tried several different ways of creating and waiting for the process (using a Shell/OpenProcess, WaitForSingleObject,etc) and I can't get it to work. I've searched all over the internet, StackOverflow and the MSDN site but nothing I've tried works. I've been messing with this for 2 days!!
Form 1 Load:
Dim ProcessProperties As New ProcessStartInfo
ProcessProperties.FileName = strExeFullPath
ProcessProperties.Arguments = " /project " & l_project
ProcessProperties.CreateNoWindow = True
Dim myProcess As Process = Process.Start(ProcessProperties)
myProcess.WaitForExit()
When Form2 is finished, it does a Me.Close() and Exit Sub in the load subroutine but the process is still showing in the TaskManager and never returns to Form1 so Form1 is in WaitForExit forever. I've tried closing every open file and connection and setting them to Nothing in Form2, Me.Dispose,etc. I've tried Application.Exit as the last line of Form2. That stupid thing will not die!! Why won't it DIE!!??!!
What am I missing?
If I go into TaskManager and kill the second application, the first one gets that notification
Keep your eyes on the ball, the real problem is that this second application is not exiting by itself. And thus myProcess.WaitForExit() isn't going to return. So this is not a problem in your code snippet.
Why the 2nd app doesn't want to quit is completely unclear from your question. Given that it is a Windows Forms app, do keep in mind that there is nobody to click the Close button of the form. Application.Exit() should make it stop, Environment.Exit() is a rude abort that cannot be veto-ed by a FormClosing event handler.
Anyway use this:
ProcessProperties.Arguments = String.Format("/project {0}", 1_project)
No leading space is required and code becomes more readable.
Cheers!
I suspect Form2 is trying to show some modal dialog (maybe a message box, maybe an unhandled exception box) before quitting. Since App2 is launched by App1, which is a service, Form2 cannot interact with the desktop and just sits there waiting for a button click that will never happen.
Try to allow the App1 service to interact with the desktop (you can find that option on the Log On tab of the service properties dialog box) and check if Form2 actually pops up a dialog before quitting.