Programming in Visual Basic

Chapter 9
Asynchronous, Concurrent, and High-Performance Programming

Post Graduate Program in Computer Science Engineering
Programming in Visual Basic   •   Post Graduate

Asynchronous, Concurrent, and High-Performance Programming

Introduction

Modern software is judged not only by correctness but also by responsiveness, throughput, scalability, and efficient resource use. A desktop application that stops responding while reading a file, a web service that processes one request at a time, or a scientific program that wastes available processor cores represents a failure of execution design. Visual Basic, through the .NET runtime, provides mature abstractions for asynchronous programming, task-based concurrency, parallel computation, cancellation, synchronization, and performance measurement.

These abstractions are related but not identical. Asynchrony concerns the non-blocking coordination of operations, especially operations involving external resources. Concurrency concerns the management of multiple logically active activities whose executions may overlap. Parallelism concerns simultaneous execution on multiple processing resources. High-performance programming concerns achieving an appropriate balance among execution time, throughput, latency, memory use, scalability, energy consumption, and maintainability.

At postgraduate level, the programmer must move beyond memorizing syntax. The central questions are: What resource limits the computation? Is the work CPU-bound or I/O-bound? Can operations safely overlap? What consistency guarantees are required? How should failure and cancellation propagate? How can performance claims be measured rather than assumed?

Learning Objectives

After completing this chapter, the learner should be able to:

  1. Distinguish synchronous, asynchronous, concurrent, and parallel execution.
  2. Design Visual Basic programs using Task, Async, and Await.
  3. Select suitable strategies for I/O-bound and CPU-bound workloads.
  4. Explain synchronization, race conditions, deadlock, starvation, and thread safety.
  5. Use cancellation, exception propagation, and bounded concurrency correctly.
  6. Analyze performance using latency, throughput, speedup, efficiency, and scalability.
  7. Apply parallel patterns and evaluate their limitations.
  8. Critically assess a concurrent design in a realistic application.

1. Fundamental Execution Models

1.1 Synchronous execution

In synchronous execution, a caller invokes an operation and waits until that operation completes. The waiting thread may be actively computing or may be idle while an external operation proceeds. Synchronous code is often easy to understand, but blocking a user-interface or server thread can reduce responsiveness and throughput.

1.2 Asynchronous execution

An asynchronous operation allows the initiating thread to perform other work while the operation is incomplete. In .NET, asynchronous methods commonly return Task or Task(Of T). The Task represents eventual completion, failure, or cancellation; it is not necessarily equivalent to a dedicated thread.

For example:

Imports System.IO
Imports System.Threading.Tasks

Public Async Function ReadTextAsync(path As String) As Task(Of String)
    Using reader As New StreamReader(path)
        Return Await reader.ReadToEndAsync()
    End Using
End Function

When Await encounters an incomplete task, control returns to the caller. When the task completes, execution resumes from the continuation point. This model is especially valuable for file, network, database, and other I/O-bound operations.

1.3 Concurrency and parallelism

Concurrency means that multiple activities are in progress during the same overall period. They may alternate on one processor or execute simultaneously on several processors. Parallelism is the special case in which activities execute simultaneously.

A single-threaded event loop can be concurrent without being parallel. Conversely, a parallel loop is both concurrent and parallel, provided multiple iterations overlap in time. Confusing these concepts leads to poor design decisions, such as creating threads for operations that merely wait for I/O.

1.4 CPU-bound and I/O-bound work

An I/O-bound operation spends much of its lifetime waiting for a device or service. Asynchronous I/O usually improves scalability because the waiting thread can be released. A CPU-bound operation spends its time performing calculations. Such work may benefit from parallel execution, but only when sufficient independent work and processor capacity exist.

The distinction is operational rather than semantic. A method that queries a database and then performs expensive encryption contains both I/O-bound and CPU-bound phases and may require a hybrid design.

2. The Task-Based Asynchronous Pattern in Visual Basic

2.1 Async and Await

The Async modifier permits a method to use Await. An asynchronous method may return Task, Task(Of T), or, in event-oriented code, Sub. Returning Task is generally preferable to Async Sub because callers can await completion and observe exceptions.

Public Async Function DownloadTextAsync(client As HttpClient,
                                        uri As String,
                                        token As CancellationToken) As Task(Of String)
    Return Await client.GetStringAsync(uri, token)
End Function

The compiler transforms an asynchronous method into a state machine. This transformation preserves logical sequential structure while allowing suspension at incomplete asynchronous operations. The method does not automatically create a new thread, and Await does not mean “run this on another thread.”

2.2 Continuations and context

After an awaited operation completes, a continuation runs. In a graphical application, the continuation may be scheduled back to the UI synchronization context so that controls can safely be updated. In library or server code, capturing a context may be unnecessary. ConfigureAwait(False) can avoid context capture in suitable library code:

Dim content = Await client.GetStringAsync(uri).ConfigureAwait(False)

This should not be applied mechanically. Code that must resume on a particular context needs that context. UI updates, for example, normally require marshaling to the UI thread.

2.3 Exceptions and cancellation

Exceptions from an awaited task are raised at the Await expression, allowing ordinary Try...Catch logic:

Try
    Dim result = Await ReadTextAsync("data.txt")
Catch ex As IOException
    Console.WriteLine($"I/O failure: {ex.Message}")
End Try

Cancellation is cooperative. A CancellationToken communicates a request; it does not forcibly terminate arbitrary code.

Public Async Function ProcessAsync(token As CancellationToken) As Task
    For i As Integer = 1 To 1000
        token.ThrowIfCancellationRequested()
        Await ProcessOneAsync(i, token)
    Next
End Function

A well-designed operation passes the token to cancellable framework methods, checks it at meaningful boundaries, and releases resources in Finally or Using blocks.

2.4 Sequential versus concurrent awaiting

The following code performs operations sequentially:

Dim first = Await GetAsync("A")
Dim second = Await GetAsync("B")

If the operations are independent, they can be initiated before awaiting either result:

Dim firstTask = GetAsync("A")
Dim secondTask = GetAsync("B")
Dim first = Await firstTask
Dim second = Await secondTask

Task.WhenAll expresses this relationship directly:

Dim tasks = urls.Select(Function(u) GetAsync(u)).ToArray()
Dim results = Await Task.WhenAll(tasks)

Unbounded creation of tasks can overload a remote service, exhaust sockets, or increase memory consumption. Concurrency must therefore be bounded when the input is large.

3. Coordination and Synchronization

3.1 Race conditions

A race condition occurs when correctness depends on the timing of competing activities. For example, counter += 1 is not generally atomic: it involves reading, adding, and writing. Two tasks may read the same old value and lose one update.

Shared mutable state should be minimized. Prefer immutable values, message passing, local accumulation, or framework-provided atomic operations. When shared state is unavoidable, select synchronization based on the critical section and contention level.

3.2 Common synchronization mechanisms

MechanismAppropriate useImportant caution
Monitor / SyncLockShort, synchronous critical sectionsNever perform long or blocking operations while holding the lock
SemaphoreSlimLimit concurrent access; supports asynchronous waitingAlways release in Finally
InterlockedSimple atomic numeric or reference updatesLimited to supported atomic operations
ConcurrentQueue(Of T)Thread-safe producer-consumer queueThread safety does not make compound workflows atomic
ReaderWriterLockSlimMany readers and few writersMore complex; may be worse under high write contention
Channel(Of T)Asynchronous producer-consumer pipelinesDefine completion and back-pressure policy

A SyncLock example is suitable for a short update:

Private ReadOnly gate As New Object()
Private total As Integer

Public Sub Add(value As Integer)
    SyncLock gate
        total += value
    End SyncLock
End Sub

An asynchronous method should generally not wait synchronously on a task while holding a lock. Prefer SemaphoreSlim when the protected operation must use Await:

Private ReadOnly gate As New SemaphoreSlim(1, 1)

Public Async Function UpdateAsync() As Task
    Await gate.WaitAsync()
    Try
        Await SaveAsync()
    Finally
        gate.Release()
    End Try
End Function

3.3 Deadlock, starvation, and livelock

A deadlock occurs when activities wait indefinitely for one another. A common lock-ordering deadlock arises when task A holds lock X and waits for Y, while task B holds Y and waits for X. Prevention techniques include consistent lock ordering, short critical sections, timeouts, and avoiding nested locks.

Starvation occurs when an activity repeatedly fails to obtain a resource. Livelock occurs when activities remain active but make no useful progress, often because they repeatedly react to one another. These failures demonstrate that “thread-safe” does not necessarily mean “progress-safe.”

4. Parallel Computation in Visual Basic

4.1 Task Parallel Library

The Task Parallel Library (TPL) provides tasks, parallel loops, task composition, and scheduling facilities. Parallel.For is useful for independent, CPU-bound iterations:

Parallel.For(0, values.Length,
    Sub(i)
        values(i) = ExpensiveFunction(values(i))
    End Sub)

The loop body must be safe under concurrent execution. It should not update a shared non-thread-safe collection or depend on iteration order unless suitable synchronization is added.

PLINQ can express parallel query operations:

Dim result = values.AsParallel().
                    Where(Function(x) x > 0).
                    Select(Function(x) Transform(x)).
                    ToArray()

Parallel query is not automatically faster. Overhead, ordering requirements, partitioning, data size, and memory bandwidth influence the result.

4.2 Data parallelism and task parallelism

Data parallelism applies the same operation to independent elements of a data set. Task parallelism executes different operations concurrently, such as parsing, validation, and persistence stages. A pipeline combines stages and may use bounded buffers to prevent a fast producer from overwhelming a slow consumer.

4.3 Reduction and thread-local state

A parallel computation often follows a map-reduce structure: map each input independently, then combine partial results. For a sum, each worker can calculate a local subtotal, after which subtotals are combined. This reduces lock contention and improves locality.

Floating-point reduction deserves special caution. Addition is mathematically associative but generally not exactly associative in finite-precision arithmetic. Different execution orders may therefore produce slightly different results. Reproducibility requirements should determine whether parallel floating-point reduction is acceptable.

5. Performance Models and Measurement

5.1 Essential metrics

Latency is the time required to complete one operation. Throughput is completed work per unit time. For a stable system, average throughput may be expressed as:

Throughput = Completed operations ÷ Elapsed time

If 12,000 requests complete in 60 seconds, throughput is 200 requests per second. This relationship assumes the measurement interval and completed-operation definition are clear.

For a workload that can be divided into a fraction p that benefits from parallel execution and a fraction 1 − p that remains serial, Amdahl’s law estimates speedup with N processors:

S(N) = 1 ÷ ((1 − p) + p ÷ N)

Here, S(N) is speedup relative to one processor, p is the parallelizable fraction between 0 and 1, and N is the number of processors. The model assumes fixed workload, negligible overhead, and ideal load distribution; real systems usually achieve less.

For p = 0.90 and N = 8:

S(8) = 1 ÷ (0.10 + 0.90 ÷ 8) = 1 ÷ 0.2125 ≈ 4.71

Thus, eight processors do not imply an eightfold speedup. Parallel efficiency is:

E(N) = S(N) ÷ N

In this example, efficiency is approximately 4.71 ÷ 8 ≈ 0.589, or 58.9 percent.

Gustafson’s law provides a complementary perspective for scaled workloads, where the problem size grows with available processors:

SG(N) = N − α × (N − 1)

Here α is the serial fraction of the scaled execution. Amdahl’s law is useful for fixed-size work; Gustafson’s law is useful when additional processors permit larger workloads.

5.2 Measurement methodology

Performance engineering should follow a disciplined cycle:

  1. Define the service-level objective, such as p95 latency or records per second.
  2. Establish a correct sequential or existing baseline.
  3. Profile to locate CPU, allocation, I/O, lock, and garbage-collection costs.
  4. Form one hypothesis about the bottleneck.
  5. Change one major factor.
  6. Benchmark representative workloads with warm-up and repeated trials.
  7. Check correctness, tail latency, memory use, and scalability.

Averages alone can hide poor user experience. Percentiles such as p95 and p99 describe tail latency: p99 is the value below which approximately 99 percent of measured requests fall. Benchmark results should report workload size, hardware, runtime version, configuration, and variance.

5.3 Common sources of overhead

Parallel execution introduces task scheduling, synchronization, partitioning, context switching, cache misses, allocation, and result-combination costs. If each unit of work is too small, overhead may exceed useful computation. Batching, thread-local accumulation, data locality, and appropriate task granularity can improve results.

6. Bounded Concurrency and Pipelines

A realistic service often receives more work than it can safely process simultaneously. A semaphore can limit active operations:

Dim limit As New SemaphoreSlim(8)

Dim tasks = items.Select(Async Function(item)
                            Await limit.WaitAsync()
                            Try
                                Return Await ProcessAsync(item)
                            Finally
                                limit.Release()
                            End Try
                        End Function).ToArray()

Dim results = Await Task.WhenAll(tasks)

The number eight is a policy choice, not a universal optimum. It should be evaluated against remote service limits, processor count, memory, and observed latency.

For continuous workloads, a producer-consumer pipeline is often superior to creating one task per item. A bounded channel or queue provides back-pressure: producers slow down when consumers cannot keep up. This protects memory and downstream systems.

A robust pipeline defines:

  • maximum buffer capacity;
  • completion signaling;
  • cancellation behavior;
  • retry and timeout policy;
  • treatment of poison messages;
  • observability for queue depth and processing time.

Retries require special care. Retrying a non-idempotent operation can duplicate effects. Exponential back-off with jitter can reduce synchronized retry storms, but it must be combined with a maximum attempt count and a clear failure path.

7. Illustrative Case Study: Concurrent Document Ingestion

The following is a fictional illustrative case designed to integrate the chapter’s concepts.

Background

A university research repository receives batches of PDF documents. Each document must be downloaded, text-extracted, classified, and indexed. The original Visual Basic service processes documents sequentially. A batch of 10,000 documents takes too long, and the user interface becomes unresponsive when progress is displayed.

Problem analysis

Profiling reveals that downloading and indexing are I/O-bound, while text classification is CPU-bound. The original implementation also creates a database connection for every document and retries failed downloads immediately without a limit. A proposed solution of creating 10,000 threads is rejected because threads consume resources and do not solve remote-service throttling.

Approach

The redesigned system uses:

  1. asynchronous HTTP and database APIs for I/O;
  2. a bounded producer-consumer pipeline;
  3. a semaphore limiting downloads to 16 concurrent operations;
  4. a CPU worker stage sized according to measured processor capacity;
  5. cancellation tokens for shutdown;
  6. a thread-safe progress channel rather than direct concurrent UI updates;
  7. idempotent indexing using a document identifier;
  8. structured logging containing document ID, stage, duration, and outcome.

The download stage places completed content into a bounded buffer. The classification stage performs CPU-intensive work in parallel. The indexing stage batches database writes where the database API permits it. Each stage releases resources in Finally blocks and applies timeouts to external calls.

Outcome

In a controlled benchmark, throughput improves substantially without increasing the number of operating-system threads linearly with the document count. More importantly, memory remains bounded because the buffer has finite capacity. When the indexing database slows, back-pressure propagates to classification and downloading rather than allowing unlimited accumulation.

The design does not assume that maximum concurrency is best. Increasing download concurrency beyond the service’s permitted rate causes throttling and raises tail latency. The final configuration is selected from measurements of throughput, p95 latency, error rate, CPU utilization, and memory use.

Learning points

The case demonstrates that performance is an end-to-end property. Asynchronous I/O, parallel CPU work, bounded concurrency, idempotency, cancellation, and observability must be designed together. A local speedup is not a system improvement if it overloads a dependency or compromises correctness.

8. Best Practices, Governance, and Contemporary Directions

Use asynchronous APIs throughout an I/O call chain; avoid blocking with .Result or .Wait() because this can waste threads and, in context-sensitive applications, contribute to deadlock. Do not use Task.Run merely to disguise synchronous I/O. It is more appropriate for isolating substantial CPU-bound work when the application architecture requires it.

Keep critical sections small, establish lock-ordering rules, and document ownership of mutable state. Prefer immutable data and message passing where practical. Make cancellation and timeout behavior explicit. Treat external operations as fallible and distinguish transient failures from permanent failures.

Concurrency also has governance implications. A service that launches excessive requests can violate a provider’s acceptable-use policy. Personal or confidential data processed concurrently must remain protected in logs, temporary buffers, and diagnostic traces. Performance optimization must not weaken authorization, auditability, encryption, or data-retention controls. Reproducibility is important in scientific and machine-learning workloads, where nondeterministic scheduling can affect floating-point results or model-training outcomes.

Contemporary .NET development increasingly combines task-based asynchronous programming with channels, dataflow-style pipelines, telemetry, and cloud-native scaling. Hardware heterogeneity, including CPUs with different core types and accelerators, makes empirical measurement more important. AI-assisted code generation may produce plausible asynchronous code, but generated code must be reviewed for unbounded concurrency, incorrect shared-state access, missing cancellation, exception loss, and privacy risks.

Challenges and Limitations

Asynchronous code can be harder to debug because execution is separated across time and sometimes across contexts. Parallel failures may be nondeterministic and difficult to reproduce. Synchronization can eliminate races but introduce contention. More concurrency can increase throughput up to a point and then reduce it. Benchmark results can be distorted by warm-up effects, caching, garbage collection, background processes, unrealistic data, or insufficient repetitions.

No performance model fully predicts a production system. Amdahl’s law abstracts away memory bandwidth, communication, scheduling, and contention; queueing behavior can make latency rise sharply as utilization approaches capacity. Therefore, analytical models should guide hypotheses, while production-like measurement validates decisions.

Chapter Summary

Asynchronous programming allows operations to make progress without blocking the initiating thread and is particularly valuable for I/O-bound workloads. In Visual Basic, Async, Await, Task, cancellation tokens, and asynchronous framework APIs provide the principal task-based model. Concurrency permits overlapping activities, while parallelism involves simultaneous execution and is most useful for sufficiently large independent CPU-bound work.

Correct concurrent programs require control of shared state, appropriate synchronization, avoidance of deadlock and starvation, and explicit handling of exceptions and cancellation. TPL facilities such as Parallel.For, PLINQ, tasks, semaphores, concurrent collections, and channels support different workload patterns. Bounded concurrency and back-pressure protect memory and external dependencies.

Performance must be measured using defined workloads and metrics such as throughput, latency, percentiles, speedup, and efficiency. Amdahl’s law illustrates why serial work limits fixed-size speedup, while real systems also incur scheduling, synchronization, memory, and communication overhead. Effective professional practice combines correctness, scalability, observability, security, governance, and empirical evaluation.

Key Terms

Asynchrony: Execution in which an operation can progress without blocking the initiating thread.

Await: Visual Basic expression that asynchronously waits for a task and resumes when it completes.

Concurrency: Overlapping progress of multiple logical activities.

Parallelism: Simultaneous execution of activities on multiple processing resources.

Task: .NET representation of an asynchronous operation or future completion.

I/O-bound: Work whose duration is dominated by waiting for external input or output.

CPU-bound: Work whose duration is dominated by computation.

Race condition: Incorrect behavior caused by timing-dependent access to shared state.

Thread safety: Property that concurrent use does not violate correctness guarantees.

Deadlock: Indefinite waiting caused by circular resource dependencies.

Starvation: Failure of an activity to obtain resources or execution opportunities.

Semaphore: Synchronization primitive that limits simultaneous access.

Cancellation token: Cooperative signal requesting that work stop.

Back-pressure: Mechanism that limits production when downstream processing is slower.

Throughput: Amount of completed work per unit time.

Latency: Time required to complete one operation.

Speedup: Ratio of baseline execution time to improved execution time.

Amdahl’s law: Model relating speedup to parallelizable work and processor count.

Idempotency: Property that repeating an operation has the same intended effect as performing it once.

Critical section: Code region requiring protected access to shared state.

Review Questions

Short Answer Questions

  1. Distinguish asynchronous execution from parallel execution.
  2. Why is Task generally preferred to Async Sub for reusable methods?
  3. What is the purpose of a CancellationToken?
  4. Define a race condition and give one prevention strategy.
  5. Why is bounded concurrency important in a service that calls external APIs?

Long Answer Questions

  1. Explain the task-based asynchronous pattern in Visual Basic, including Async, Await, continuations, exception propagation, and context capture.
  2. Compare SyncLock, SemaphoreSlim, Interlocked, concurrent collections, and channels.
  3. Explain Amdahl’s law, its assumptions, and its implications for parallel program design.
  4. Describe how a producer-consumer pipeline can provide back-pressure and controlled resource usage.
  5. Develop a performance-measurement methodology for a concurrent Visual Basic application.

Analytical / Application Questions

  1. A program has a parallelizable fraction of 0.90 and is executed using eight processors. Use Amdahl’s law to estimate speedup and efficiency. Explain why the result is below eightfold speedup.
  2. Design a concurrent document-processing system containing download, CPU classification, and database-indexing stages. Identify where asynchrony, parallelism, cancellation, bounded concurrency, and idempotency should be applied.
  3. A parallel application reports higher average throughput but worse p99 latency and occasional duplicate database records. Diagnose possible causes and propose a technically justified redesign.

Multiple Choice Questions with Answers

Question 1

Which statement best describes the relationship between asynchronous execution and parallel execution?

A. Asynchronous execution always creates a new operating-system thread.

B. Asynchronous execution can overlap waiting without requiring simultaneous CPU execution.

C. Parallel execution is possible only for I/O-bound operations.

D. The two terms have exactly the same meaning.

Correct Answer: B. Asynchronous execution can overlap waiting without requiring simultaneous CPU execution.

Question 2

Which return type is generally appropriate for a reusable Visual Basic asynchronous method that produces no value?

A. Integer

B. Thread

C. Task

D. Async Sub

Correct Answer: C. Task

Question 3

What is the principal purpose of a CancellationToken?

A. To forcibly terminate any thread immediately

B. To request cooperative cancellation of an operation

C. To guarantee that an operation succeeds

D. To increase the processor clock frequency

Correct Answer: B. To request cooperative cancellation of an operation

Question 4

Which synchronization primitive is especially suitable for limiting the number of simultaneous asynchronous operations?

A. SemaphoreSlim

B. StringBuilder

C. StreamReader

D. Random

Correct Answer: A. SemaphoreSlim

Question 5

Under Amdahl’s law, what limits the speedup of a fixed-size workload?

A. The serial fraction of the workload

B. The number of variable names

C. The screen resolution

D. The number of comments in the code

Correct Answer: A. The serial fraction of the workload

Question 6

What problem does back-pressure primarily address in a producer-consumer pipeline?

A. It increases the number of duplicate messages.

B. It prevents a fast producer from overwhelming downstream stages.

C. It removes the need for error handling.

D. It guarantees zero latency.

Correct Answer: B. It prevents a fast producer from overwhelming downstream stages.

Question 7

Why can parallel floating-point reduction produce slightly different results from sequential reduction?

A. Floating-point addition is not necessarily exactly associative.

B. Processors cannot add floating-point values in parallel.

C. Parallel loops always change the input values.

D. The compiler converts all numbers to strings.

Correct Answer: A. Floating-point addition is not necessarily exactly associative.

Question 8

Which practice is most likely to cause thread starvation or deadlock in context-sensitive applications?

A. Awaiting an asynchronous API

B. Using immutable local variables

C. Blocking synchronously on a task with .Result or .Wait().

D. Passing a cancellation token to an API

Correct Answer: C. Blocking synchronously on a task with .Result or .Wait().

Question 9

A workload completes 12,000 operations in 60 seconds. What is its throughput?

A. 20 operations per second

B. 120 operations per second

C. 200 operations per second

D. 720,000 operations per second

Correct Answer: C. 200 operations per second

Question 10

Which property is especially important when retrying an operation that may be executed more than once?

A. Idempotency

B. Random screen placement

C. UI thread affinity

D. Large lock duration

Correct Answer: A. Idempotency

Programming in Visual Basic
Chapter 9 of 9