Skip to content
AgileLogics
7 min read

Async and Await

Programs spend a lot of time waiting — for a web request, a database, a file. Async and Await let code wait without blocking a thread, so a desktop app stays responsive and a server can handle more requests.

Module Program
    Async Function FetchPriceAsync(product As String) As Task(Of Integer)
        Await Task.Delay(100)          ' simulate a slow network call
        Return product.Length * 10
    End Function

    Async Function RunAsync() As Task
        Console.WriteLine("fetching...")
        Dim tea = Await FetchPriceAsync("tea")
        Dim coffee = Await FetchPriceAsync("coffee")
        Console.WriteLine($"total: {tea + coffee}")
    End Function

    Sub Main()
        RunAsync().GetAwaiter().GetResult()
    End Sub
End Module
fetching...
total: 90
  • An Async Function returns a Task (no result) or Task(Of T) (a result of type T). By convention its name ends in Async.
  • Await pauses the method until the task finishes, without blocking the thread, then continues with the result.
  • VB doesn't allow Async Sub Main, so a console app starts the async code from Main and waits for it with .GetAwaiter().GetResult().

Running tasks concurrently

The two awaits above run one after another. Start independent tasks first, then await them together with Task.WhenAll:

Module Program
    Async Function WorkAsync(name As String, ms As Integer) As Task(Of String)
        Await Task.Delay(ms)
        Return $"{name} done"
    End Function

    Async Function RunAsync() As Task
        Dim sw = Stopwatch.StartNew()
        Dim tasks = {WorkAsync("A", 300), WorkAsync("B", 300), WorkAsync("C", 300)}
        Dim results = Await Task.WhenAll(tasks)
        Console.WriteLine(String.Join(", ", results))
        Console.WriteLine($"took under 600 ms: {sw.ElapsedMilliseconds < 600}")
    End Function

    Sub Main()
        RunAsync().GetAwaiter().GetResult()
    End Sub
End Module
A done, B done, C done
took under 600 ms: True

Three 300 ms operations finished in about 300 ms in total. Task.WhenAny returns as soon as the first one finishes.

A real HTTP request

' illustrative: needs network access
Imports System.Net.Http

Module Program
    Private ReadOnly client As New HttpClient()

    Async Function PageSizeAsync(url As String) As Task(Of Integer)
        Dim html = Await client.GetStringAsync(url)
        Return html.Length
    End Function

    Sub Main()
        Dim size = PageSizeAsync("https://learn.microsoft.com").GetAwaiter().GetResult()
        Console.WriteLine($"{size} characters")
    End Sub
End Module

Create one HttpClient and reuse it; creating one per request can use up network sockets under load.

Async in Windows Forms

Event handlers are the one place where Async Sub is right:

' illustrative: inside a Windows Forms form
Private Async Sub LoadButton_Click(sender As Object, e As EventArgs) Handles LoadButton.Click
    LoadButton.Enabled = False
    StatusLabel.Text = "Loading..."
    Dim text = Await File.ReadAllTextAsync("big-report.txt")
    ResultBox.Text = text
    StatusLabel.Text = "Done"
    LoadButton.Enabled = True
End Sub

While the file loads, the window keeps responding. After Await, the code continues on the UI thread, so it can update controls safely.

Common mistakes

  • Async Sub anywhere except event handlers — callers can't await it, and its exceptions can crash the program. Use Async Function ... As Task.
  • Calling .Result or .Wait() on a task from UI code — it can deadlock. Await all the way up.
  • Forgetting Await: the method keeps going before the work is done, and exceptions go unobserved.