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
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 FunctionMarking 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
1. What does an Async Function in VB.NET typically return?
2. What happens when execution reaches an Await expression whose Task has not yet completed?
3. Why is calling .Result on a Task from a WinForms UI thread dangerous?
4. What is the purpose of ConfigureAwait(False)?
5. What does Task.WhenAny do?
Was this page helpful?
You May Also Like
Windows Forms Basics
Get started with Windows Forms in VB.NET — forms, controls, the visual designer, event handling, layout, and the form lifecycle.
Events and Delegates in VB.NET
Understand how delegates provide type-safe function references in VB.NET, and how the Event/RaiseEvent/WithEvents system builds the observer pattern on top of them.
LINQ in VB.NET
Explore Language Integrated Query (LINQ) in VB.NET, from query syntax and lambda-based method syntax to deferred execution and aggregate operations.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics