|
// 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.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Editor.Shared.Utilities
{
/// <summary>
/// Base class that allows some helpers for detecting whether we're on the main WPF foreground thread, or
/// a background thread. It also allows scheduling work to the foreground thread at below input priority.
/// </summary>
internal class ForegroundThreadAffinitizedObject
{
private readonly IThreadingContext _threadingContext;
internal IThreadingContext ThreadingContext => _threadingContext;
public ForegroundThreadAffinitizedObject(IThreadingContext threadingContext, bool assertIsForeground = false)
{
_threadingContext = threadingContext ?? throw new ArgumentNullException(nameof(threadingContext));
// ForegroundThreadAffinitizedObject might not necessarily be created on a foreground thread.
// AssertIsForeground here only if the object must be created on a foreground thread.
if (assertIsForeground)
{
// Assert we have some kind of foreground thread
Contract.ThrowIfFalse(threadingContext.HasMainThread);
AssertIsForeground();
}
}
public bool IsForeground()
=> _threadingContext.JoinableTaskContext.IsOnMainThread;
public void AssertIsForeground()
{
var whenCreatedThread = _threadingContext.JoinableTaskContext.MainThread;
var currentThread = Thread.CurrentThread;
// In debug, provide a lot more information so that we can track down unit test flakiness.
// This is too expensive to do in retail as it creates way too many allocations.
Debug.Assert(currentThread == whenCreatedThread,
"When created thread id : " + whenCreatedThread?.ManagedThreadId + "\r\n" +
"When created thread name: " + whenCreatedThread?.Name + "\r\n" +
"Current thread id : " + currentThread?.ManagedThreadId + "\r\n" +
"Current thread name : " + currentThread?.Name);
// But, in retail, do the check as well, so that we can catch problems that happen in the wild.
Contract.ThrowIfFalse(_threadingContext.JoinableTaskContext.IsOnMainThread);
}
public void AssertIsBackground()
=> Contract.ThrowIfTrue(IsForeground());
/// <summary>
/// A helpful marker method that can be used by deriving classes to indicate that a
/// method can be called from any thread and is not foreground or background affinitized.
/// This is useful so that every method in deriving class can have some sort of marker
/// on each method stating the threading constraints (FG-only/BG-only/Any-thread).
/// </summary>
public static void ThisCanBeCalledOnAnyThread()
{
// Does nothing.
}
public Task InvokeBelowInputPriorityAsync(Action action, CancellationToken cancellationToken = default)
{
if (IsForeground() && !IsInputPending())
{
// Optimize to inline the action if we're already on the foreground thread
// and there's no pending user input.
action();
return Task.CompletedTask;
}
else
{
return Task.Factory.SafeStartNewFromAsync(
async () =>
{
await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
action();
},
cancellationToken,
TaskScheduler.Default);
}
}
/// <summary>
/// Returns true if any keyboard or mouse button input is pending on the message queue.
/// </summary>
protected static bool IsInputPending()
{
// The code below invokes into user32.dll, which is not available in non-Windows.
if (PlatformInformation.IsUnix)
{
return false;
}
// The return value of GetQueueStatus is HIWORD:LOWORD.
// A non-zero value in HIWORD indicates some input message in the queue.
var result = NativeMethods.GetQueueStatus(NativeMethods.QS_INPUT);
const uint InputMask = NativeMethods.QS_INPUT | (NativeMethods.QS_INPUT << 16);
return (result & InputMask) != 0;
}
}
}
|