100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Async/Await in VB.NET

Learn how the Async and Await keywords let VB.NET applications perform non-blocking I/O and long-running work without freezing the UI or wasting threads.

.NET IntegrationIntermediate10 min readJul 10, 2026
Analogies

Why Asynchronous Programming

Synchronous I/O operations — reading a file, calling a web API, querying a database — block the calling thread until they complete, which in a WinForms or WPF application freezes the UI because the same thread that handles button clicks and repaints the window is stuck waiting. Asynchronous programming lets a method start a long-running operation, release the thread back to do other work, and resume exactly where it left off once the operation completes, without the overhead of manually managing callbacks or spinning up dedicated threads for every I/O call.

🏏

Cricket analogy: Blocking the UI thread on I/O is like a captain standing frozen at the toss, unable to set any field or make any decision until a slow satellite call about pitch conditions finally connects.

The Async and Await Keywords

vbnet
Public Async Function GetTotalDownloadSizeAsync(urls As List(Of String)) As Task(Of Long)
    Dim total As Long = 0
    Using client As New HttpClient()
        For Each url In urls
            Dim data As Byte() = Await client.GetByteArrayAsync(url)
            total += data.Length
        Next
    End Using
    Return total
End Function

Marking a method Async allows it to use the Await operator inside its body; the method's return type becomes Task for a Sub-like method with no return value, or Task(Of T) when it returns a value of type T. When execution hits an Await expression, control returns to the caller immediately if the awaited Task hasn't completed yet, and the rest of the method runs later — via a compiler-generated state machine — once that Task finishes, resuming on a captured context by default.

🏏

Cricket analogy: Await pausing at a point and resuming later is like a rain-delayed match resuming from the exact ball and over it stopped at, rather than restarting the whole innings from scratch.

Task-Based Asynchronous Pattern (TAP)

The Task-based Asynchronous Pattern standardizes async APIs around Task and Task(Of T): Task.Run offloads CPU-bound work onto a thread pool thread when there's no natural async I/O API to await, Task.WhenAll awaits multiple tasks concurrently and completes once all of them finish (useful for firing off several independent web requests at once instead of awaiting them one at a time), and Task.WhenAny completes as soon as the first of several tasks finishes, which is handy for implementing timeouts by racing a real operation against a Task.Delay.

🏏

Cricket analogy: Task.WhenAll is like waiting for every fielder to confirm they're in position before the umpire signals play can start — the whole group must be ready, not just one.

Prefer Async Function over Async Sub everywhere except top-level event handlers (like a Button's Click handler), which must be Async Sub because event delegates require a Sub signature. Async Sub methods can't be awaited by their caller and their exceptions can't be caught normally by the caller, making them hard to compose and test — 'async all the way' with Task-returning methods is the safer default.

Exception Handling in Async Code

Exceptions thrown inside an Async Function are captured and stored on the returned Task rather than thrown immediately; they surface at the point where the Task is awaited, so wrapping the Await expression in a normal Try/Catch block catches the exception just as it would for synchronous code. When you use Task.WhenAll to await several tasks and more than one of them fails, awaiting the combined task only re-throws the first exception directly, though all exceptions remain accessible via the AggregateException on each individual faulted Task if you inspect them separately.

🏏

Cricket analogy: An exception surfacing only when awaited is like a DRS decision that's captured the moment it happens but only announced to the crowd once the third umpire actually reviews the replay.

Never call .Result or .Wait() on a Task from a UI thread (or any thread with a synchronization context) to force synchronous waiting on async code — this is a classic deadlock: the async method wants to resume on the captured UI context, but that context's thread is itself blocked waiting for the Result, so neither side can ever proceed. Await the Task properly instead, all the way up the call stack.

ConfigureAwait

By default, Await captures the current SynchronizationContext (such as the WinForms UI context) and resumes the continuation on it, which is necessary when the code after Await needs to touch UI controls. In library or non-UI code that never touches the UI, calling Await someTask.ConfigureAwait(False) tells the runtime it doesn't need to resume on that original context, allowing the continuation to run on whatever thread pool thread is available, which avoids unnecessary context-switching overhead and reduces the risk of contributing to deadlocks in code that might otherwise be called synchronously.

🏏

Cricket analogy: ConfigureAwait(False) is like a substitute fielder who doesn't insist on returning to their exact original position after fielding the ball, happy to settle wherever is convenient — avoiding unnecessary repositioning.

  • Async/Await lets long-running I/O run without blocking the calling thread, keeping UI applications responsive.
  • An Async Function returns Task or Task(Of T); Await pauses execution until that Task completes, resuming via a compiler-generated state machine.
  • Task.Run offloads CPU-bound work to the thread pool; Task.WhenAll and Task.WhenAny coordinate multiple concurrent tasks.
  • Prefer Async Function over Async Sub except for event handlers, since Async Sub can't be awaited or easily exception-handled by callers.
  • Exceptions thrown in async code surface when the Task is awaited, so Try/Catch around Await works as expected.
  • Never call .Result or .Wait() on a Task from a UI thread — it risks a classic deadlock with the captured synchronization context.
  • ConfigureAwait(False) skips resuming on the captured context, useful in library code that doesn't touch the UI.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#VBNETStudyNotes#AsyncAwaitInVBNET#Async#Await#NET#Asynchronous#Concurrency#StudyNotes#SkillVeris