Click or drag to resize

SyncContext Structure

Used by public async library methods to reset the current task SynchronizationContext so that continuations won't be marshalled back to the current thread, improving performance.

Namespace:  Neon.Tasks
Assembly:  Neon.Common (in Neon.Common.dll) Version: 2.1.0
public struct SyncContext : INotifyCompletion

The SyncContext type exposes the following members.

Public propertyStatic memberClearAsync
await this singleton to clear the current synchronization context for the scope of the current method. The original context will be restored when the method returns.
Public propertyIsCompleted
INTERNAL USE ONLY: Do not call this directly.
Public propertyStatic memberIsDisabled

Optionally disables context resetting globally. This provides an escape hatch for situations where an application needs to revert back to the default synchronization context behavior. This turns await SyncContext.ClearAsync calls into a NOP.

Note Note
Most applications should never need to set this.
Public methodEquals
Indicates whether this instance and a specified object are equal.
(Inherited from ValueType.)
Public methodGetAwaiter
INTERNAL USE ONLY: Do not call this directly.
Public methodGetHashCode
Returns the hash code for this instance.
(Inherited from ValueType.)
Public methodGetResult
INTERNAL USE ONLY: Do not call this directly.
Public methodGetType
Gets the Type of the current instance.
(Inherited from Object.)
Public methodOnCompleted
INTERNAL USE ONLY: Do not call this directly.
Public methodToString
Returns the fully qualified type name of this instance.
(Inherited from ValueType.)

This class was adapted from this blog post:

The only real changes are that I renamed the structure and converted it into a singleton.

By default, awaited tasks will always be completed on the calling thread. This is typically required for applications with user interfaces such as XAML or Windows Forms applications because the awaiter will typically need the results on the single UI thread to be able to manuiplate the user interface. This behavior is great for UI apps, but can introduce substantial overhead for server applications or even portions of UI apps where an async operation performs a lot other async operations.

The problem is that typical servers applications run asynchronously on thread pool threads and don't generally care about the thread they're running on. The default await behavior requires that each async operation be completed on the calling thread, but under load, it's very likely that the calling thread will have been reused for another operation. This means that when the async operation completes, the task will need to wait for the calling thread to be returned to the thread pool before it can be fetched and task continuation executed on it. This can result in a substantial performance hit just to manage this process as well what may be a substantial amount of time for the original thread to make it back to the pool.

As the blog post describes, developers are encouraged to call ConfigureAwait(Boolean), passing false for every async call where the result doesn't need to be marshalled back to the original synchronization context. Non-UI class libraries typically don't care about this.

The problem is that to do this properly, MSFT recommends that you call Task.ConfigureAwait(false) on EVERYasync call you made in these situations. This is pretty ugly and will be tough to enforce on large projects over long periods of time because it's easy to miss one.

This struct implements a custom awaiter that saves the current synchronization context and then clears it for the rest of the current method execution and then restores the original context when when the method returns. This means that every subsequent await performed in the method will simply fetch a pool thread to continue execution, rather than to the original context thread. To accomplish this, you'll simply await ClearAsync at or near the top of your method:

using Neon.Task;

public async Task<string> HelloAsync()
    await SyncContext.ClearAsync;

    await DoSomthingAsync();
    await DoSomethingElseAsync();

    return "Hello World!";
Note Note
ClearAsync is not a method so you don't need to pass any parameters.

This call clears the current synchronization context such that the subsequent async calls will each marshal back to threads obtained from the thread pool and due to compiler async magic, the original synchronization context will be restored before the HelloAsync() method returns.

See Also