VB.Net + WebService: main form unresponsive on load - vb.net

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.

Related

How to handle long running tasks in VB.NET forms?

I am currently working on a VB.NET form that automatically create Word documents according to an Excel file and a few extra data asked by the form (Project Name, Customer Name, Use SQL, ...).
This procedure works fine and takes approximatelly 1 or 2 minutes to complete.
The issue is that all my script is in ButtonGenerate.Click Handler. So when the Generate button is pressed the form window is bricked and it's impossible to Cancel...
It shouldn't be in a Click handler. Opening a new thread for that long task seems better. But Multithreading isn't very familiar to me.
I tryed launching the script with
ThreadPool.QueueUserWorkItem(...
but my Generate Sub sets labels and update a Progress Bar in the main form, so I doesn't work unless I use
Me.Invoke(New MethodInvoker(Sub()
label.Text = "..."
ProgressBar.Value = 10
' ...
End Sub)
each time I need to update something on the form and I can't even retrieve any new push of a button with that (A cancel button would be nice).
This is basically my code :
Public Class TestFichesAutomation
Private Sub BtnGenerate_Click(sender As Object, e As EventArgs) Handles BtnGenerate.Click
System.Threading.ThreadPool.QueueUserWorkItem(Sub() Generate())
End Sub
Public Sub Generate()
' Check user options, retrieve Excel Data, SQL, Fill in custom classes, create Word docs (~ 1 minute)
End Sub
So How would you handle that script ? Is Threading even a good solution ?
Thanks a lot for your help ^^ and for the useful doc.
My app now open a new thread and uses 2 custom classes to act like buffers :
Private Async Sub Btn_Click(sender As Object, e As EventArgs) Handles Btn.Click
myProgress = New Progress
' a custom class just for the UI with the current task, current SQL connection status and progress value in %
_Options.ProjectName = TextBoxProjectName.Text
_Options.CustomerName = TextBoxCustomerName.Text
...
' Fill in a custom "_Options" private class to act as a buffer between the 2 thread (the user choices)
Loading = New Loading()
Me.Visible = False
Loading.Show() ' Show the Loading window (a ProgressBar and a label : inputLine)
Task.Run(Function() Generate(Progress, _Options))
Me.Visible = True
End Sub
Public Async Function Generate(ByVal myProgress As Progress, ByVal Options As Options) As Task(Of Boolean)
' DO THE LONG JOB and sometimes update the UI :
myProgress.LoadingValue = 50 ' %
myProgress.CurrentTask= "SQL query : " & ...
Me.Invoke(New MethodInvoker(Sub() UpdateLoading()))
' Check if the task has been cancelled ("Cancelled" is changed by a passvalue from the Loading window):
If myProgress.Cancelled = True Then ...
' Continue ...
End Function
Public Shared Sub UpdateLoading()
MyForm.Loading.ProgressBar.Value = myProgress.LoadingValue
MyForm.Loading.inputLine.Text = myProgress.CurrentTask
' ...
End Sub
You should look into using the Async/Await structure
if the work you need to do is CPU bound, i like using Task.Run() doc here
By making your event handler Async and having it Await the work, you prevent the UI from locking up and avoid the use of Invoke in most cases.
ex:
Private Async Sub Btn_Click(sender As Object, e As EventArgs) Handles Btn.Click
Dim Result As Object = Await Task.Run(Function() SomeFunction())
'after the task returned by Task.Run is completed, the sub will continue, thus allowing you to update the UI etc..
End Sub
For progress reporting with Async/Await you might be interested in this

Making sure async tasks complete before vb.net application terminates

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

Diffrence between async/await and ContinueWith

Consider this example:
Private Async Function ComputeText() As Task(Of String)
Dim result As String = Await Task.Run(Function()
'do whatever
Return "Done"
End Function)
Return result
End Function
Now could anyone tell me whether there is diffrence between those two button event handlers? From my perspective this is the same but 'better approach' is to use the 1st one, am i right?
'1st:
Private Async Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
LabelCounter.Text = "Running"
Dim value As string = Await ComputeText()
LabelCounter.Text = value
End Sub
'2nd:
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
LabelCounter.Text = "Running"
Dim task = ComputeText().ContinueWith(
Sub(x)
LabelCounter.Invoke(
Sub()
LabelCounter.Text = x.Result
End Sub)
End Sub)
End Sub
Await does use ContinueWith under the hood. However, ContinueWith is a dangerous API and should not be used directly. As I describe in my post on task continuations:
ContinueWith has an unfortunate choice of default sceduler; it uses TaskScheduler.Current rather than TaskScheduler.Default.
ContinueWith does not understand asynchronous delegates, so it will return an extra task wrapper that you have to unwrap (by calling Unwrap).
ContinueWith does not have appropriate default option arguments for asynchronous code (e.g., TaskContinuationOptions.DenyChildAttach and TaskContinuationOptions.ExecuteSynchronously).
For these reasons, you should use Await instead of ContinueWith for asynchronous code.
Also, it's shorter and prettier. :)

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 task.delay locks browser

I am trying to do a very simple job in vb.net 4.5 framework; Create and run a simple Async Task that will symbolize (making several database calls asynchrnously/ parallel).
I am using vs2012 and vb.net Very simple MVC app and one control.
The code is simple>
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim newTask As Task(Of String) = TryPause()
lblResults2.Text = newTask.Result
End Sub
Public Async Function TryPause() As Task(Of String)
Await Task.Delay(100)
Return "hello World"
End Function
Code runs fine when the "task.delay" is remarked out.
But if it stays inside of the code, the Browser locks up.
Notice: thread.sleep works fine....
What am i missing?
As #SLaks correctly pointed out, the Result is causing a deadlock. I explain this in more detail on my blog and in a recent MSDN article.