Saleem
Saleem

Reputation: 730

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?

Upvotes: 1

Views: 508

Answers (3)

Victor Zakharov
Victor Zakharov

Reputation: 26434

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:

Upvotes: 1

Saleem
Saleem

Reputation: 730

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.

Upvotes: 0

Victor Zakharov
Victor Zakharov

Reputation: 26434

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:

Thanks to @PanagiotisKanavos for pointing me in the right direction.

Upvotes: 1

Related Questions