Class JoinableTaskContext
- Namespace
- Microsoft.VisualStudio.Threading
- Assembly
- Microsoft.VisualStudio.Threading.dll
A common context within which joinable tasks may be created and interact to avoid deadlocks.
public class JoinableTaskContext : IHangReportContributor, IDisposable
- Inheritance
-
JoinableTaskContext
- Implements
- Inherited Members
Remarks
There are three rules that should be strictly followed when using or interacting with JoinableTasks:
- If a method has certain thread apartment requirements (STA or MTA) it must either:
a) Have an asynchronous signature, and asynchronously marshal to the appropriate
thread if it isn't originally invoked on a compatible thread.
The recommended way to switch to the main thread is:
b) Have a synchronous signature, and throw an exception when called on the wrong thread. In particular, no method is allowed to synchronously marshal work to another thread (blocking while that work is done). Synchronous blocks in general are to be avoided whenever possible.await JoinableTaskFactory.SwitchToMainThreadAsync();
- When an implementation of an already-shipped public API must call asynchronous code
and block for its completion, it must do so by following this simple pattern:
JoinableTaskFactory.Run(async delegate { await SomeOperationAsync(...); });
- If ever awaiting work that was started earlier, that work must be Joined.
For example, one service kicks off some asynchronous work that may later become
synchronously blocking:
Then later that async work becomes blocking:JoinableTask longRunningAsyncWork = JoinableTaskFactory.RunAsync(async delegate { await SomeOperationAsync(...); });
or perhaps:longRunningAsyncWork.Join();
Note however that this extra step is not necessary when awaiting is done immediately after kicking off an asynchronous operation.await longRunningAsyncWork;
Constructors
JoinableTaskContext()
Initializes a new instance of the JoinableTaskContext class assuming the current thread is the main thread and Current will provide the means to switch to the main thread from another thread.
public JoinableTaskContext()
Remarks
When Current is null at the time this constructor is invoked, requests to switch to the main thread using SwitchToMainThreadAsync(CancellationToken) will not result in any thread switch. This is appropriate for unit test environments where there is no main thread to switch to or processes which otherwise do not define a main thread. Thread safety concern: When configured without a synchronization context, code that requests the main thread as a means of avoiding concurrency may malfunction due to data race conditions.
JoinableTaskContext(Thread?, SynchronizationContext?)
Initializes a new instance of the JoinableTaskContext class.
public JoinableTaskContext(Thread? mainThread = null, SynchronizationContext? synchronizationContext = null)
Parameters
mainThread
ThreadThe thread to switch to in SwitchToMainThreadAsync(CancellationToken). If null, the current thread will be assumed to be the main thread.
synchronizationContext
SynchronizationContextThe synchronization context to use to switch to the main thread.
If null is specified (or the argument is omitted), the current synchronization context will be used. If Current is also null, requests to switch to the main thread using SwitchToMainThreadAsync(CancellationToken) will not result in any thread switch. This is appropriate for unit test environments where there is no main thread to switch to or processes which otherwise do not define a main thread. Thread safety concern: When configured without a synchronization context, code that requests the main thread as a means of avoiding concurrency may malfunction due to data race conditions.
Properties
Factory
Gets the factory which creates joinable tasks that do not belong to a joinable task collection.
public JoinableTaskFactory Factory { get; }
Property Value
IsNoOpContext
Gets a value indicating whether this instance is not associated with any main thread (e.g. created with CreateNoOpContext()).
public bool IsNoOpContext { get; }
Property Value
Remarks
This allows library code to skip some additional work in the environments that do not have a main thread.
IsOnMainThread
Gets a value indicating whether the caller is executing on the main thread.
public bool IsOnMainThread { get; }
Property Value
IsWithinJoinableTask
Gets a value indicating whether the caller is currently running within the context of a joinable task.
public bool IsWithinJoinableTask { get; }
Property Value
Remarks
Use of this property is generally discouraged, as any operation that becomes a no-op when no ambient JoinableTask is present is very cheap. For clients that have complex algorithms that are only relevant if an ambient joinable task is present, this property may serve to skip that for performance reasons.
MainThread
Gets the main thread that can be shared by tasks created by this context.
public Thread MainThread { get; }
Property Value
NoMessagePumpSynchronizationContext
Gets a SynchronizationContext which, when applied, suppresses any message pump that may run during synchronous blocks of the calling thread.
protected virtual SynchronizationContext NoMessagePumpSynchronizationContext { get; }
Property Value
Remarks
The default implementation of this property is effective in builds of this assembly that target the .NET Framework. But on builds that target the portable profile, it should be overridden to provide an effective platform-specific solution.
Methods
Capture()
Captures the caller's context and serializes it as a string that is suitable for application via a subsequent call to RunAsync(Func<Task>, string?, JoinableTaskCreationOptions).
public string? Capture()
Returns
Remarks
To optimize calling patterns, this method returns null even when inside a JoinableTask context when this JoinableTaskContext was initialized without a SynchronizationContext, which means no main thread exists and thus there is no need to capture and reapply tokens.
CreateCollection()
Creates a collection for in-flight joinable tasks.
public JoinableTaskCollection CreateCollection()
Returns
- JoinableTaskCollection
A new joinable task collection.
CreateDefaultFactory()
Creates a factory without a JoinableTaskCollection.
protected virtual JoinableTaskFactory CreateDefaultFactory()
Returns
Remarks
Used for initializing the Factory property.
CreateFactory(JoinableTaskCollection)
Creates a joinable task factory that automatically adds all created tasks to a collection that can be jointly joined.
public virtual JoinableTaskFactory CreateFactory(JoinableTaskCollection collection)
Parameters
collection
JoinableTaskCollectionThe collection that all tasks should be added to.
Returns
CreateNoOpContext()
Initializes a new instance of the JoinableTaskContext class that is configured to no-op on calls to SwitchToMainThreadAsync(CancellationToken).
public static JoinableTaskContext CreateNoOpContext()
Returns
- JoinableTaskContext
A new instance of JoinableTaskContext.
Remarks
This method is equivalent to calling the JoinableTaskContext() constructor with the Current property first set to null. This entry point however will have the same behavior regardless of the value of Current.
The caller's thread will still be captured for use by such properties as MainThread and IsOnMainThread. These properties generally have no effect except as used by application-specific code beyond this library.
This method is useful for creating a JoinableTaskContext in a unit test environment or in a process that does not have a main thread, but which includes code that requires an instance of JoinableTaskContext or JoinableTaskFactory. Such code can receive the instance returned by this method and use it in a normal way but no main thread switches will be honored.
Thread safety concern: Because main thread switches will not be honored, code that requests the main thread as a means of avoiding concurrency may malfunction due to data race conditions.
Dispose()
public void Dispose()
Dispose(bool)
Disposes managed and unmanaged resources held by this instance.
protected virtual void Dispose(bool disposing)
Parameters
GetHangReport()
Contributes data for a hang report.
protected virtual HangReportContribution GetHangReport()
Returns
- HangReportContribution
The hang report contribution. Null values should be ignored.
IsMainThreadBlocked()
Gets a value indicating whether the main thread is blocked for the caller's completion.
public bool IsMainThreadBlocked()
Returns
IsMainThreadMaybeBlocked()
Gets a very likely value whether the main thread is blocked for the caller's completion. It is less accurate when the UI thread blocking task just starts and hasn't been blocked yet, or the dependency chain is just removed. However, unlike IsMainThreadBlocked(), this implementation is lock free, and faster in high contention scenarios.
public bool IsMainThreadMaybeBlocked()
Returns
OnFalseHangDetected(TimeSpan, Guid)
Invoked when an earlier hang report is false alarm.
protected virtual void OnFalseHangDetected(TimeSpan hangDuration, Guid hangId)
Parameters
OnHangDetected(TimeSpan, int, Guid)
Invoked when a hang is suspected to have occurred involving the main thread.
protected virtual void OnHangDetected(TimeSpan hangDuration, int notificationCount, Guid hangId)
Parameters
hangDuration
TimeSpanThe duration of the current hang.
notificationCount
intThe number of times this hang has been reported, including this one.
hangId
GuidA random GUID that uniquely identifies this particular hang.
Remarks
A single hang occurrence may invoke this method multiple times, with increasing
values in the hangDuration
parameter.
SuppressRelevance()
Conceals any JoinableTask the caller is associated with until the returned value is disposed.
public JoinableTaskContext.RevertRelevance SuppressRelevance()
Returns
- JoinableTaskContext.RevertRelevance
A value to dispose of to restore visibility into the caller's associated JoinableTask, if any.
Remarks
In some cases asynchronous work may be spun off inside a delegate supplied to Run,
so that the work does not have privileges to re-enter the Main thread until the
Run(Func<Task>) call has returned and the UI thread is idle.
To prevent the asynchronous work from automatically being allowed to re-enter the Main thread,
wrap the code that calls the asynchronous task in a using
block with a call to this method
as the expression.
this.JoinableTaskContext.RunSynchronously(async delegate {
using(this.JoinableTaskContext.SuppressRelevance()) {
var asyncOperation = Task.Run(async delegate {
// Some background work.
await this.JoinableTaskContext.SwitchToMainThreadAsync();
// Some Main thread work, that cannot begin until the outer RunSynchronously call has returned.
});
}
// Because the asyncOperation is not related to this Main thread work (it was suppressed),
// the following await *would* deadlock if it were uncommented.
////await asyncOperation;
});