// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. namespace Microsoft.VisualStudio.Threading { using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; /// /// A customizable source of instances. /// public class JoinableTaskContextNode { /// /// The inner JoinableTaskContext. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly JoinableTaskContext context; /// /// A single joinable task factory that itself cannot be joined. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] private JoinableTaskFactory? nonJoinableFactory; /// /// Initializes a new instance of the class. /// /// The inner JoinableTaskContext. public JoinableTaskContextNode(JoinableTaskContext context) { Requires.NotNull(context, nameof(context)); this.context = context; } /// /// Gets the factory which creates joinable tasks /// that do not belong to a joinable task collection. /// public JoinableTaskFactory Factory { get { if (this.nonJoinableFactory is null) { JoinableTaskFactory? factory = this.CreateDefaultFactory(); Interlocked.CompareExchange(ref this.nonJoinableFactory, factory, null); } return this.nonJoinableFactory; } } /// /// Gets the main thread that can be shared by tasks created by this context. /// public Thread MainThread { get { return this.context.MainThread; } } /// /// Gets a value indicating whether the caller is executing on the main thread. /// public bool IsOnMainThread => this.context.IsOnMainThread; /// /// Gets the inner wrapped context. /// public JoinableTaskContext Context { get { return this.context; } } /// /// Creates a joinable task factory that automatically adds all created tasks /// to a collection that can be jointly joined. /// /// The collection that all tasks should be added to. public virtual JoinableTaskFactory CreateFactory(JoinableTaskCollection collection) { return this.context.CreateFactory(collection); } /// /// Creates a collection for in-flight joinable tasks. /// /// A new joinable task collection. public JoinableTaskCollection CreateCollection() { return this.context.CreateCollection(); } /// /// Conceals any JoinableTask the caller is associated with until the returned value is disposed. /// /// A value to dispose of to restore visibility into the caller's associated JoinableTask, if any. /// /// 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 /// 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; /// }); /// /// /// public JoinableTaskContext.RevertRelevance SuppressRelevance() { return this.context.SuppressRelevance(); } /// /// Gets a value indicating whether the main thread is blocked for the caller's completion. /// public bool IsMainThreadBlocked() { return this.context.IsMainThreadBlocked(); } /// /// Invoked when a hang is suspected to have occurred involving the main thread. /// /// Describes the hang in detail. /// /// A single hang occurrence may invoke this method multiple times, with increasing /// values in the values /// in the parameter. /// protected internal virtual void OnHangDetected(JoinableTaskContext.HangDetails details) { Requires.NotNull(details, nameof(details)); // Preserve backward compatibility by forwarding the call to the older overload. this.OnHangDetected(details.HangDuration, details.NotificationCount, details.HangId); } /// /// Invoked when an earlier hang report is false alarm. /// /// The duration of the total waiting time. /// A GUID that uniquely identifies the earlier hang report. protected internal virtual void OnFalseHangDetected(TimeSpan hangDuration, Guid hangId) { } /// /// Invoked when a hang is suspected to have occurred involving the main thread. /// /// The duration of the current hang. /// The number of times this hang has been reported, including this one. /// A random GUID that uniquely identifies this particular hang. /// /// A single hang occurrence may invoke this method multiple times, with increasing /// values in the parameter. /// protected virtual void OnHangDetected(TimeSpan hangDuration, int notificationCount, Guid hangId) { } /// /// Creates a factory without a . /// /// /// Used for initializing the property. /// protected virtual JoinableTaskFactory CreateDefaultFactory() { return this.context.CreateDefaultFactory(); } /// /// Registers with the inner to receive hang notifications. /// /// A value to dispose of to cancel hang notifications. protected IDisposable RegisterOnHangDetected() { return this.context.RegisterHangNotifications(this); } } }