Making sure async tasks complete before vb.net application terminates - vb.net

I'm creating a vb.net desktop application. This application includes some asynchronous functions. When the user closes the application via the red X in the upper-right corner, there is some logic to possibly run one or more of these async functions. The problem is, the program terminates before they are complete. I figured using "Await" in my call would do that, but apparently not.
I found this thread that talks about using ManualResetEvent, but I'm having trouble understanding all of it, especially since the question is in the context of a console app, and the MSDN documentation the answer links to is about specifying threads, not simply using async tasks. As an attempt at using it anyway, I tried adding this to my main form:
Public resetEvent As ManualResetEvent = New ManualResetEvent(False)
And immediately after the call to one of these functions, I added this (quote includes the call):
Await activeCount.SerializeAsync(activeCount)
resetEvent.WaitOne()
And at the end of my async function itself, before returning the Task, added this:
frmMain.resetEvent.Set()
I don't think I'm using that right, though. The program still terminates before it's complete anyway.
Even before that, I figured the best place for such a thing would be in ApplicationEvents MyApplication_Shutdown, but I'm not sure how to know if such a function is still running at that point.
So what is the best way to make sure all my async functions complete before the application terminates in this situation?
Thank you!
UPDATE AFTER ACCEPTED ANSWER:
Though F0r3v3r-A-N00b's answer worked, I realized I need to use a dialog in certain cases. I couldn't call that within the background worker because the dialog is on the GUI thread, not the background thread. I tried moving things around so I'd call the dialog first, then make the background worker and all that, but for whatever reason I couldn't get it to work.
Long story short, I got around it by simply making a synchronous version of my functions, and so I could say 'if the user terminated the program and I need to call any of these functions before closing, call the synchronous versions instead'. That works. Thanks!

Try this. Create a new project. Add 1 label and backgroundworker to your form. Paste this in your form's code area:
Public Class Form1
Dim taskCompleted As Boolean = False
Dim taskIsrunning As Boolean = False
Private Sub BackgroundWorker1_DoWork(sender As System.Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Threading.Thread.Sleep(5000)
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
taskCompleted = True
taskIsRunning = False
Label1.Text = "Background task completed."
Me.Close()
End Sub
Private Sub Form1_FormClosing(sender As Object, e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
If taskIsRunning Then
e.Cancel = True
Exit Sub
End If
If Not taskCompleted Then
taskIsRunning = True
Label1.Text = "Starting background task."
BackgroundWorker1.RunWorkerAsync()
Label1.Text = "Background task is running."
e.Cancel = True
End If
End Sub
End Class

Related

VB.net PrintForm Not Working in New Thread

I am developing a e-filing app and I need to print an adhesive label with some info to attach to the physical folder.
I already designed the label as a Form put the logo and everything that I need there. Then on the Form.Shown event I put the command to print:
Me.PrintLabelForm.Print() (This is VisualStudio PowerPack Control)
And here is where I bump into a problem. The print out is totally empty (I already changed margins setup the printer, etc). The issue is that the form is not actually fully loaded, I switch the method to the print preview and the controls are there but they are empty.
I tried several approaches but I have been not able to do this automatically. One solution that I found was to have a button to do the Me.PrintLabelForm.Print() then it works because the form is already fully loaded and displayed but this is not an option. I need the form to open automatically, print and close.
An option that I think it should work will be to have a new thread with a timer then printing so I did this:
Private Sub LabelPrint_Load(sender As Object, e As EventArgs) Handles MyBase.Load
End Sub
Private Sub LabelPrint_Shown(sender As Object, e As EventArgs) Handles Me.Shown
PrintLabelForm.PrinterSettings.DefaultPageSettings.Margins.Left = 0.1
PrintLabelForm.PrinterSettings.DefaultPageSettings.Margins.Right = 0.1
PrintLabelForm.PrinterSettings.DefaultPageSettings.Margins.Top = 0.1
PrintLabelForm.PrinterSettings.DefaultPageSettings.Margins.Bottom = 0.1
PrintLabelForm.PrinterSettings.DefaultPageSettings.Landscape = True
Dim PrintThread As New System.Threading.Thread(AddressOf PrintSub)
PrintThread.Start()
End Sub
Private Sub PrintSub()
Threading.Thread.Sleep(1000)
Me.PrintLabelForm.Print()
Me.Close()
End Sub
The idea was to have the PrintSub to give the app enough time to finish to render the whole thing then print but I am getting this error:
**An unhandled exception of type 'System.Exception' occurred in Microsoft.VisualBasic.PowerPacks.dll
Additional information: The window being printed must be visible and contain focus.**
So I wonder how to make this thread have the window form in focus in order to be able to print.
That is all. Thanks for all the help.
Always work with the form only from main thread.
You found it right – form printing will not run from new thread.
When you do any actions on forms, you must perform all the work from Dispatcher thread. It is the thread on which all event methods run. If you fail doing so, you can encounter many side effects. (Not only problem with printing. I've been there and this advice from senior Windows programmer helped me to get things back to normal.) So do not use form printing from any other thread.
If you want a workaround for this, print form to the image (in main thread) and then you can print the image using new thread.
This has nothing to do with .NET, this is related to internals of Windows Forms technology. Welcome to Windows programming.
I manage to solve it putting this line in the Form.Shown
PrintLabelForm.Print(Me, PrintForm.PrintOption.ClientAreaOnly)
I don't know why or how but it works.
Thanks to all of you guys for your help. Let's hope I don't find myself trying to do stuff when the form is fully displayed.
This is my full code let's hope it works for someone else:
Imports Microsoft.VisualBasic.PowerPacks.Printing
Public Class PrintAdhesiveLabel
Private Sub LabelPrint_Load(sender As Object, e As EventArgs) Handles MyBase.Load
End Sub
Private Sub LabelPrint_Shown(sender As Object, e As EventArgs) Handles Me.Shown
PrintLabelForm.PrinterSettings.DefaultPageSettings.Margins.Left = 0.1
PrintLabelForm.PrinterSettings.DefaultPageSettings.Margins.Right = 0.1
PrintLabelForm.PrinterSettings.DefaultPageSettings.Margins.Top = 0.1
PrintLabelForm.PrinterSettings.DefaultPageSettings.Margins.Bottom = 0.1
PrintLabelForm.PrinterSettings.DefaultPageSettings.Landscape = True
PrintLabelForm.Print(Me, PrintForm.PrintOption.ClientAreaOnly)
Me.Close()
End Sub
End Class
Perhaps this is relevant:
Only the form that currently has focus can be printed by using this
method. If you have set the Form property to another form before
calling this method, the image of the form may not be rendered as
expected. To avoid this, call the Focus method of the form before you
call Print.
So call Me.PrintLabelForm.Focus() before calling Me.PrintLabelForm.Print():
Private Sub PrintSub()
Threading.Thread.Sleep(1000)
Me.PrintLabelForm.Focus()
Me.PrintLabelForm.Print()
Me.Close()
End Sub

WinForms.IllegalCrossThreadCall with filewatcher

I'm new to Visual Basic and overall kind of new to coding in general.
Currently I work on a program which uses a filewatcher. But If I try this:
Public Class Form1
Private WithEvents fsw As IO.FileSystemWatcher
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
fsw = New IO.FileSystemWatcher("PATH")
fsw.EnableRaisingEvents = True
' fsw.Filter = "*.settings"
End Sub
Private Sub GetSettingsFromFile()
Some Code
More Code
CheckBox1.Checked = True
End Sub
Private Sub fsw_Changed(sender As Object, e As FileSystemEventArgs) Handles fsw.Changed
fsw.EnableRaisingEvents = False 'this is set because the file is changed many times in rapid succesion so I need to stop the Filewatcher from going of 200x (anyone has a better idea to do this?)
Threading.Thread.Sleep(100)
GetSettingsFromFile()
fsw.EnableRaisingEvents = True 'enabling it again
End Sub
End Class
But when I do this (trying to change anyhting in the form) I get this error:
System.InvalidOperationException (WinForms.IllegalCrossThreadCall)
It wont stop the program from working, but I want to understand what is wrong here and why the debugger is throwing this at me
regards
The event is being raised on a secondary thread. Any changes to the UI must be made on the UI thread. You need to marshal a method call to the UI thread and update the UI there. Lots of information around on how to do that. Here's an example:
Private Sub UpdateCheckBox1(checked As Boolean)
If CheckBox1.InvokeRequired Then
'We are on a secondary thread so marshal a method call to the UI thread.
CheckBox1.Invoke(New Action(Of Boolean)(AddressOf UpdateCheckBox1), checked)
Else
'We are on the UI thread so update the control.
CheckBox1.Checked = checked
End If
End Sub
Now you simply call that method wherever you are and whatever thread you're on. If you're already on the UI thread then the control will just be updated. If you're on a secondary thread then the method will invoke itself a second time, this time on the UI thread, and the control will be updated in that second invocation.

VB.Net + WebService: main form unresponsive on load

I have a small VB.Net project with link to sql using web service (SOAP).
I have to make sure that all forms are totally responsive no matter what, and it's working pretty well. My only problem is on loading the application!
The main start-up form has only single line of code:
Private Async Sub frmMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Await objWebService.GetCurrentSessionsAsync
End Sub
But while this "awaitable" code is being executed the form is unresponsive, frozen and wait cursor is displayed.
Any idea on what might be causing this issue and how to handle it?
In regard to your answer, the code can be much cleaner if you don't combine different programming patterns, check this out:
Private Async Sub frmMain_Load(sender As Object,
e As EventArgs) Handles MyBase.Load
Dim res = Await GetCurrentSessionsAsync()
End Sub
Private Async Function GetCurrentSessionsAsync() As Task(Of com.services.Server)
Try
Return Await Task.Factory.
StartNew(Function() objWebService.GetCurrentSessions)
Catch ex As Exception
Glob.ErrorLog("GetCurrentSessions", ex, True)
Return New com.services.Server
End Try
End Function
References:
try-catch (C# Reference)
Async Return Types (C# and Visual Basic)
The key problem is that Async does not magically make your method asynchronous. It only lets compiler know that your method will have Await keywords, and that the code needs to be converted into a state machine. Any code that is not awaited is executed synchronously, even if the method is marked as Async. Consider the following example:
Private Async Sub Form1_Load(sender As Object,
e As EventArgs) Handles MyBase.Load
Await LongRunning1() 'opens the form, then asynchronously changes
'Text property after 2 seconds
End Sub
Private Async Function LongRunning1() As Task
Await Task.Factory.StartNew(Sub() Threading.Thread.Sleep(2000))
Me.Text = "Finished LongRunning1"
End Function
Here a long running process, Thread.Sleep as an example, is wrapped into a Task, and there is an Await keyword. It tells the compiler to wait for the statements inside the task to finish, before executing the next line. Without the Await, the Text property would be set immediately.
Now suppose you have some long running synchronous code in your Async method:
Private Async Sub Form1_Load(sender As Object,
e As EventArgs) Handles MyBase.Load
Await LongRunning2() 'synchronously waits 2 seconds, opens the form,
'then asynchronously changes Text property after 2 seconds
End Sub
Private Async Function LongRunning2() As Task
Threading.Thread.Sleep(2000)
Await LongRunning1()
Me.Text = "Finished LongRunning2"
End Function
Notice in this case it synchronously waits for the Thread.Sleep to finish, so for the end user you app appears as hanging. Bottom line is - you have to know which method calls can be long running, and wrap them into a task based await model. Otherwise you may be seeing the problem you are seeing.
If this sounds too complicated, you can fire up a background worker (.NET 2.0+), or use TPL (.NET 4.0+) to start a task. If you wish to go into lower level, threading is available since .NET 1.1. Then display some wait/progress window/overlay on top of the form/control, for which the functionality is not yet available. Check these out:
Loading data from DB asynchronously in win forms
Await async with event handler
Thanks to #PanagiotisKanavos for pointing me in the right direction.
So here what it is (I have to say that the answer of Neolisk and Panagiotis led me to the solution):
What made my loading form unresponsive is what appeared to be a bug in web services, only the first call of my web service would produce this issue. So If the first call was made after form load, on another event, I would face same problem.
To fix this, I changed the way I call my first method through web service using TaskCompletionSource variable, and calling my first method using Thread. I'll post my before/after code to be sure I delivered my fix clearly.
Before:
Private Async Sub frmMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim res = Await objWebService.GetCurrentSessionsAsync
End Sub
After:
Private Async Sub frmMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim res = Await GetCurrentSessionsAsync()
End Sub
Dim _tcServer As New TaskCompletionSource(Of MyProject.com.services.Server)
Private Async Function GetCurrentSessionsAsync() As Task(Of com.services.Server)
Try
Dim th As New System.Threading.Thread(AddressOf GetCurrentSessions)
th.Start()
Return Await _tcServer.Task
Catch ex As Exception
Return New MyProject.com.services.Server
End Try
End Function
Private Sub GetCurrentSessions()
Try
Dim res = objWebService.GetCurrentSessions
_tcServer.SetResult(res)
Catch ex As Exception
Glob.ErrorLog("GetCurrentSessions", ex, True)
End Try
End Sub
I hope this can help others in the future.
Thank you.

VB.NET Marquee Progress Until Process Exits

While I have some VBScript experience, this is my first attempt at creating a very simple VB.NET (Windows Forms Application) wrapper for a command line application. Please be kind!
I have a very simple GUI with two buttons that both do an action and I'd like to show a marquee progress bar until the action (read: the process) is complete (read: exits).
The 'save' button does this:
Dim SaveEXE As Process = Process.Start("save.exe", "/whatever /arguments")
From there I'm starting the marquee progress bar:
ProgressBar1.Style = ProgressBarStyle.Marquee
ProgressBar1.MarqueeAnimationSpeed = 60
ProgressBar1.Refresh()
I thought I could use SaveEXE.WaitForExit() but the Marquee starts, then stops in the middle until the process exits. Not very useful for those watching; they'll think it hung.
I thought maybe I could do something like this but that causes my VB.Net app to crash
Do
ProgressBar1.Style = ProgressBarStyle.Marquee
ProgressBar1.MarqueeAnimationSpeed = 60
ProgressBar1.Refresh()
Loop Until SaveEXE.ExitCode = 0
ProgressBar1.MarqueeAnimationSpeed = 60
ProgressBar1.Refresh()
I'm not entirely sure what needs to be done, short of getting some formal training.
You can use the new Async/Await Feature of .NET 4.5 for this:
Public Class Form1
Private Async Sub RunProcess()
ProgressBar1.Visible = True
Dim p As Process = Process.Start("C:\test\test.exe")
Await Task.Run(Sub() p.WaitForExit())
ProgressBar1.Visible = False
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
RunProcess()
End Sub
End Class
Note the Async keyword in the declaration of the RunProcess sub and the Await keyword.
You run the WaitForExit in another thread and by using Await the application basically stops at this line as long as the task takes to complete.
This however also keeps your GUI reponsive meanwhile. For the example I just show the progressbar (it is invisible before) and hide it once the task is complete.
This also avoids any Application.DoEvents hocus pocus.

InvalidCastException with WebBrowser.IsBusy or ReadyState (VB .NET)

I was playing around with the method that was suggested as an answer to another of my questions (Automate website log-in and form filling?), and noticed something curious.
The answer to the above question was to use a series of javascript calls as URL's in order to fill in a web form and submit it. I have been trying to do this automatically inside a VB .NET program with no success.
The original example I was given doesn't work, presumably because you are waiting on the same thread as that in which the WebBrowser control is working:
WebBrowser1.Navigate("http://www.google.com")
Do While WebBrowser1.IsBusy OrElse WebBrowser1.ReadyState <> WebBrowserReadyState.Complete
Threading.Thread.Sleep(1000)
Application.DoEvents()
Loop
WebBrowser1.Navigate("javascript:function%20f(){document.forms[0]['q'].value='stackoverflow';}f();")
Threading.Thread.Sleep(2000) 'wait for javascript to run
WebBrowser1.Navigate("javascript:document.forms[0].submit()")
Threading.Thread.Sleep(2000) 'wait for javascript to run
If you don't wait at all, it doesn't work either, of course. The URL you originally browse to is interrupted. But interestingly, you cannot perform the "navigations" to the javascript calls without delay, either.
So I've tried two other methods: using the DocumentCompleted event to wait to browse to the nest URL until the browser has finished loading the page. Unfortunately, DocumentCompleted does not always fire, and doesn't seem to fire after each javascript URL.
The second method I tried was to put the wait in a separate thread:
Private Delegate Sub SetTextDelegate(ByVal TheText As String)
Private Sub delSetText(ByVal TheText As String)
WebBrowser1.Navigate(TheText)
End Sub
Private Sub BrowseTo(ByVal URL As String)
If WebBrowser1.InvokeRequired Then
Me.BeginInvoke(New SetTextDelegate(AddressOf delSetText), URL)
Else
WebBrowser1.Navigate(URL)
End If
End Sub
Private Sub TargetURL()
BrowseTo("http://www.google.com")
End Sub
Private Sub TypeSomethingIn()
BrowseTo("javascript:function%20f(){document.forms[0]['g'].value='test';}f();")
End Sub
Private Sub SubmitForm()
BrowseTo("javascript:document.forms[0].submit()")
End Sub
Private Sub Wait()
While True
If WebBrowser1.ReadyState = WebBrowserReadyState.Complete Then Exit Sub
Threading.Thread.Sleep(100)
End While
End Sub
Private Sub AutoBrowse()
TargetURL()
Wait()
TypeSomethingIn()
Wait()
SubmitForm()
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim t As Threading.Thread
t = New Threading.Thread(AddressOf AutoBrowse)
t.Start()
End Sub
The curious thing is that the check of ReadyState (or IsBusy, for that matter) in the wait loop will sometimes throw a InvalidCastException. Presumably calls to these are not thread safe? I have no idea. If I put the offending call inside a Try block, the wait loop just fails to work. In fact, it even seems the exception "persists" to screw everything up, because even stepping through the code with the try block Visual Studio freezes for a good 10 to 20 seconds (it does the same without the try block).
Any ideas?
One of the most interesting issues I had experienced and which for I
was not able to find a solution in inet - was problem related to
WebBrowser control. The thing is that when I was trying to access the
Document property of the WebBrowser control instance, I was getting
"Invalid cast exception". The thing is that the WebBrowser control is
designed to work in one thread. So to fix this you must only check the
InvokeRequired property and if it's value is true, then call the logic
from the delegate, given into browser.Invoke(...) method.
Source
Following msdn article: "There are four methods on a control that are thread safe to call: Invoke, BeginInvoke, EndInvoke and CreateGraphics and InvokeRequired property"
Therefore call to WebBrowser1.ReadyState in Sub Wait is not thread safe