File: Shared\Utilities\ThreadingContext.cs
Web Access
Project: src\src\EditorFeatures\Core\Microsoft.CodeAnalysis.EditorFeatures.csproj (Microsoft.CodeAnalysis.EditorFeatures)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.VisualStudio.Threading;
 
namespace Microsoft.CodeAnalysis.Editor.Shared.Utilities;
 
/// <summary>
/// Implements <see cref="IThreadingContext"/>, which provides an implementation of
/// <see cref="VisualStudio.Threading.JoinableTaskFactory"/> to Roslyn code.
/// </summary>
/// <remarks>
/// <para>The <see cref="VisualStudio.Threading.JoinableTaskFactory"/> is constructed from the
/// <see cref="VisualStudio.Threading.JoinableTaskContext"/> provided by the MEF container, if available. If no
/// <see cref="VisualStudio.Threading.JoinableTaskContext"/> is available, a new instance is constructed using the
/// synchronization context of the current thread as the main thread.</para>
/// </remarks>
[Export(typeof(IThreadingContext))]
[Shared]
internal sealed class ThreadingContext : IThreadingContext, IDisposable
{
    private readonly CancellationTokenSource _disposalTokenSource = new();
 
    [ImportingConstructor]
    [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
    public ThreadingContext(JoinableTaskContext joinableTaskContext)
    {
        HasMainThread = joinableTaskContext.MainThread.IsAlive;
        JoinableTaskContext = joinableTaskContext;
        JoinableTaskFactory = joinableTaskContext.Factory;
        ShutdownBlockingTasks = new JoinableTaskCollection(JoinableTaskContext);
        ShutdownBlockingTaskFactory = JoinableTaskContext.CreateFactory(ShutdownBlockingTasks);
    }
 
    /// <inheritdoc/>
    public bool HasMainThread
    {
        get;
    }
 
    /// <inheritdoc/>
    public JoinableTaskContext JoinableTaskContext
    {
        get;
    }
 
    /// <inheritdoc/>
    public JoinableTaskFactory JoinableTaskFactory
    {
        get;
    }
 
    public JoinableTaskCollection ShutdownBlockingTasks { get; }
 
    private JoinableTaskFactory ShutdownBlockingTaskFactory { get; }
 
    public CancellationToken DisposalToken => _disposalTokenSource.Token;
 
    public JoinableTask RunWithShutdownBlockAsync(Func<CancellationToken, Task> func)
    {
        return ShutdownBlockingTaskFactory.RunAsync(() =>
        {
            DisposalToken.ThrowIfCancellationRequested();
            return func(DisposalToken);
        });
    }
 
    public void Dispose()
    {
        // https://github.com/Microsoft/vs-threading/blob/main/doc/cookbook_vs.md#how-to-write-a-fire-and-forget-method-responsibly
        _disposalTokenSource.Cancel();
 
        try
        {
            // Block Dispose until all async work has completed.
            JoinableTaskContext.Factory.Run(ShutdownBlockingTasks.JoinTillEmptyAsync);
        }
        catch (OperationCanceledException)
        {
            // this exception is expected because we signaled the cancellation token
        }
        catch (AggregateException ex)
        {
            // ignore AggregateException containing only OperationCanceledException
            ex.Handle(inner => inner is OperationCanceledException);
        }
    }
}