Synchronous vs Asynchronous Delegates in Legacy C
In legacy C# (.NET Framework versions prior to modern .NET Core),
developers handled method execution through delegates either
synchronously using direct invocation or asynchronously using the
Asynchronous Programming Model (APM). The fundamental difference between
the two approaches lies in thread management, responsiveness, and
execution flow: synchronous invocation runs directly on the calling
thread and blocks execution until the operation completes, whereas the
legacy asynchronous pattern (BeginInvoke and
EndInvoke) dispatches work to a ThreadPool thread to allow
non-blocking, parallel operations.
Synchronous Delegate Invocation
Synchronous delegate execution occurs when a delegate is invoked
directly, either using standard method call syntax or explicitly via the
.Invoke() method:
Func<int, int, int> add = (a, b) => a + b;
int result = add(5, 10); // Or add.Invoke(5, 10);When invoked synchronously:
- Thread Execution: The delegate executes on the exact thread that called it.
- Blocking Behavior: The calling thread suspends execution until the delegate finishes its work. If the operation is I/O-heavy or computationally demanding, it halts UI responsiveness or stalls server request processing.
- Exception Propagation: Any exceptions thrown within
the target method propagate directly up the calling thread's call stack,
making them catchable with a standard
try-catchblock enclosing the invocation.
Legacy
Asynchronous Pattern (BeginInvoke and
EndInvoke)
Before the introduction of the Task-based Asynchronous Pattern (TAP)
and the async/await keywords in C# 5.0,
delegates automatically provided built-in asynchronous methods generated
by the compiler: BeginInvoke and
EndInvoke.
Func<int, int, int> add = (a, b) => a + b;
// Initiates execution on a ThreadPool thread
IAsyncResult asyncResult = add.BeginInvoke(5, 10, null, null);
// Do other work on the calling thread...
// Retrieves the return value and blocks if work is not yet finished
int result = add.EndInvoke(asyncResult);When invoked asynchronously:
- Thread Execution:
BeginInvokedispatches the method call to a worker thread managed by the CLRThreadPool. - Non-Blocking Initiation:
BeginInvokereturns immediately, providing anIAsyncResulttoken that tracks execution status. This leaves the calling thread free to continue processing other tasks. - Completion and Retrieval: The caller must call
EndInvoketo retrieve return values, capture out/ref parameters, and clean up internal resources. If the worker thread has not finished whenEndInvokeis called, the calling thread blocks at that point until completion. - Callback Support:
BeginInvokeallowed passing anAsyncCallbackdelegate, which executed automatically on the worker thread once the task was complete, eliminating the need to poll or manually wait for the result. - Exception Handling: Exceptions thrown inside the
delegate are not surfaced at the time of
BeginInvoke. Instead, they are captured and re-thrown when the caller callsEndInvoke.
Key Technical Differences
| Feature | Synchronous Invocation
(Invoke) |
Legacy Asynchronous
(BeginInvoke/EndInvoke) |
|---|---|---|
| Execution Context | Executes on the caller's thread | Executes on a ThreadPool
background thread |
| Caller Blocking | Fully blocks the caller until completion | Returns immediately; blocks only when
EndInvoke is called |
| Resource Overhead | Minimal; standard method-call overhead | Higher; requires thread allocation,
context switching, and state tracking via IAsyncResult |
| Exception Handling | Caught immediately at the point of call | Stored internally and re-thrown only when
calling EndInvoke |
| Completion Notification | Implicit upon return of the method | Handled via polling, waiting on a
WaitHandle, or an AsyncCallback |
Obsolescence in Modern .NET
In modern .NET (.NET Core, .NET 5, and later), delegate-based
BeginInvoke and EndInvoke are unsupported and
throw a PlatformNotSupportedException. The runtime replaced
this pattern with Task-based alternatives
(Task.Run or native asynchronous I/O methods), which offer
better performance, integration with cancellation tokens, and
streamlined control flow through async and
await.