|
// 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);
}
}
}
|