提交 1c480f57 编写于 作者: J Jared Parsons

Make foreground thread scheduling predictable

This change makes foreground scheduling code predictable in the IDE
tests.  Instead of using a static to hold the data we use instance
values.  The static data the instance values are populated from are not
100% predictable but far more so than they used to be.
上级 7d3e2123
......@@ -310,6 +310,7 @@ Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "CSharpAnalyzerDriver", "src
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Test\Utilities\Shared\TestUtilities.projitems*{76c6f005-c89d-4348-bb4a-391898dbeb52}*SharedItemsImports = 4
src\Compilers\Core\AnalyzerDriver\AnalyzerDriver.projitems*{edc68a0e-c68d-4a74-91b7-bf38ec909888}*SharedItemsImports = 4
src\Test\Utilities\Shared\TestUtilities.projitems*{ccbd3438-3e84-40a9-83ad-533f23bcfca5}*SharedItemsImports = 4
src\ExpressionEvaluator\VisualBasic\Source\ResultProvider\BasicResultProvider.projitems*{ace53515-482c-4c6a-e2d2-4242a687dfee}*SharedItemsImports = 4
......
......@@ -122,7 +122,7 @@ public void Cancel()
},
_cancellationTokenSource.Token,
TaskContinuationOptions.OnlyOnRanToCompletion,
ForegroundThreadAffinitizedObject.ForegroundTaskScheduler);
ForegroundThreadAffinitizedObject.DefaultForegroundThreadData.TaskScheduler);
continuedTask.CompletesAsyncOperation(asyncToken);
}
......
......@@ -117,6 +117,7 @@
<InternalsVisibleToTest Include="Roslyn.Services.Editor.UnitTests" />
<InternalsVisibleToTest Include="Roslyn.Services.Editor.UnitTests2" />
<InternalsVisibleToTest Include="Roslyn.Services.Editor.VisualBasic.UnitTests" />
<InternalsVisibleToTest Include="Roslyn.Services.Test.Utilities" />
<InternalsVisibleToTest Include="Roslyn.Test.Utilities.Desktop" />
<InternalsVisibleToTest Include="Roslyn.VisualStudio.CSharp.UnitTests" />
<InternalsVisibleToTest Include="Roslyn.VisualStudio.Services.UnitTests" />
......@@ -802,4 +803,4 @@
<ImportGroup Label="Targets">
<Import Project="..\..\..\build\Targets\VSL.Imports.targets" />
</ImportGroup>
</Project>
\ No newline at end of file
</Project>
......@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
......@@ -33,6 +34,8 @@ public ForegroundNotificationService()
_workQueue = new PriorityQueue();
_lastProcessedTimeInMS = Environment.TickCount;
Debug.Assert(IsValid());
Debug.Assert(IsForeground());
Task.Factory.SafeStartNewFromAsync(ProcessAsync, CancellationToken.None, TaskScheduler.Default);
}
......
......@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.VisualStudio.Text;
......@@ -77,11 +78,11 @@ void IController<TModel>.OnModelUpdated(TModel result)
this.OnModelUpdated(result);
}
IAsyncToken IController<TModel>.BeginAsyncOperation()
IAsyncToken IController<TModel>.BeginAsyncOperation(string filePath, int lineNumber)
{
AssertIsForeground();
VerifySessionIsActive();
return _asyncListener.BeginAsyncOperation(_asyncOperationId);
return _asyncListener.BeginAsyncOperation(_asyncOperationId, filePath: filePath, lineNumber: lineNumber);
}
protected void VerifySessionIsActive()
......
......@@ -3,6 +3,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
......@@ -34,7 +35,8 @@ private void ThreadStart()
while (true)
{
var task = _tasks.Take();
this.TryExecuteTask(task);
bool ret = this.TryExecuteTask(task);
Debug.Assert(ret);
}
}
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Runtime.CompilerServices;
using Microsoft.CodeAnalysis.Shared.TestHooks;
namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense
......@@ -7,7 +8,7 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense
internal interface IController<TModel>
{
void OnModelUpdated(TModel result);
IAsyncToken BeginAsyncOperation();
IAsyncToken BeginAsyncOperation([CallerFilePath] string filePath = "", [CallerLineNumber]int lineNumber = 0);
void StopModelComputation();
}
}
......@@ -146,7 +146,7 @@ private void StartSelectedItemUpdateTask(int delay, bool updateUIWhenDone)
t => PushSelectedItemsToPresenter(t.Result),
cancellationToken,
TaskContinuationOptions.OnlyOnRanToCompletion,
ForegroundThreadAffinitizedObject.ForegroundTaskScheduler).CompletesAsyncOperation(asyncToken);
ForegroundThreadAffinitizedObject.DefaultForegroundThreadData.TaskScheduler).CompletesAsyncOperation(asyncToken);
}
}
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
......@@ -9,46 +10,31 @@
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
internal enum ForegroundThreadDataKind
{
private static Thread s_foregroundThread;
private static TaskScheduler s_foregroundTaskScheduler;
internal static Thread ForegroundThread
{
get { return s_foregroundThread; }
}
Wpf,
StaUnitTest,
Unknown
}
internal static TaskScheduler ForegroundTaskScheduler
{
get { return s_foregroundTaskScheduler; }
}
internal sealed class ForegroundThreadData
{
internal readonly Thread Thread;
internal readonly TaskScheduler TaskScheduler;
internal readonly ForegroundThreadDataKind Kind;
// HACK: This is a dangerous way of establishing the 'foreground' thread affinity of an
// AppDomain. This method should be deleted in favor of forcing derivations of this type
// to either explicitly inherit WPF Dispatcher thread or provide an explicit thread
// they believe to be the foreground.
static ForegroundThreadAffinitizedObject()
internal ForegroundThreadData(Thread thread, TaskScheduler taskScheduler, ForegroundThreadDataKind kind)
{
Initialize(force: true);
Thread = thread;
TaskScheduler = taskScheduler;
Kind = kind;
}
// This static initialization method *must* be invoked on the UI thread to ensure that the static 'foregroundThread' field is correctly initialized.
public static ForegroundThreadAffinitizedObject Initialize(bool force = false)
internal static ForegroundThreadData CreateDefault()
{
if (s_foregroundThread != null && !force)
{
return new ForegroundThreadAffinitizedObject();
}
TaskScheduler taskScheduler;
ForegroundThreadDataKind kind;
s_foregroundThread = Thread.CurrentThread;
#if true
s_foregroundTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
#else
var previousContext = SynchronizationContext.Current;
try
{
......@@ -56,21 +42,71 @@ public static ForegroundThreadAffinitizedObject Initialize(bool force = false)
// So instead of using the default priority which is above user input, we use Background priority which is 1 level
// below user input.
SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext(Dispatcher.CurrentDispatcher, DispatcherPriority.Background));
s_foregroundTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
kind = previousContext?.GetType().FullName == "System.Windows.Threading.DispatcherSynchronizationContext"
? ForegroundThreadDataKind.Wpf
: ForegroundThreadDataKind.Unknown;
}
finally
{
SynchronizationContext.SetSynchronizationContext(previousContext);
}
#endif
return new ForegroundThreadAffinitizedObject();
return new ForegroundThreadData(Thread.CurrentThread, taskScheduler, kind);
}
}
public ForegroundThreadAffinitizedObject(bool assertIsForeground = false)
/// <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 static readonly ForegroundThreadData s_fallbackForegroundThreadData;
private static ForegroundThreadData s_defaultForegroundThreadData;
private readonly ForegroundThreadData _foregroundThreadData;
internal static ForegroundThreadData FallbackForegroundThreadData
{
get { return s_fallbackForegroundThreadData; }
}
internal static ForegroundThreadData DefaultForegroundThreadData
{
get { return s_defaultForegroundThreadData ?? s_fallbackForegroundThreadData; }
set { s_defaultForegroundThreadData = value; }
}
internal ForegroundThreadData ForegroundThreadData
{
get { return _foregroundThreadData; }
}
internal Thread ForegroundThread
{
get { return _foregroundThreadData.Thread; }
}
internal TaskScheduler ForegroundTaskScheduler
{
get { return _foregroundThreadData.TaskScheduler; }
}
// HACK: This is a dangerous way of establishing the 'foreground' thread affinity of an
// AppDomain. This method should be deleted in favor of forcing derivations of this type
// to either explicitly inherit WPF Dispatcher thread or provide an explicit thread
// they believe to be the foreground.
static ForegroundThreadAffinitizedObject()
{
s_fallbackForegroundThreadData = ForegroundThreadData.CreateDefault();
}
public ForegroundThreadAffinitizedObject(ForegroundThreadData foregroundThreadData = null, bool assertIsForeground = false)
{
_foregroundThreadData = foregroundThreadData ?? DefaultForegroundThreadData;
// For sanity's sake, ensure that our idea of "foreground" is the same as WPF's
Contract.ThrowIfFalse(Application.Current == null || Application.Current.Dispatcher.Thread == ForegroundThreadAffinitizedObject.s_foregroundThread);
Contract.ThrowIfFalse(Application.Current == null || Application.Current.Dispatcher.Thread == ForegroundThread);
// ForegroundThreadAffinitizedObject might not necessarily be created on a foreground thread.
// AssertIsForeground here only if the object must be created on a foreground thread.
......@@ -82,11 +118,24 @@ public ForegroundThreadAffinitizedObject(bool assertIsForeground = false)
public bool IsForeground()
{
return Thread.CurrentThread == ForegroundThreadAffinitizedObject.s_foregroundThread;
return Thread.CurrentThread == ForegroundThread;
}
/// <summary>
/// Ensure this is a supported scheduling context like Wpf or explicit STA scheduler.
/// </summary>
/// <returns></returns>
public bool IsValid()
{
return _foregroundThreadData.Kind != ForegroundThreadDataKind.Unknown;
}
public void AssertIsForeground()
{
#if DEBUG
Debug.Assert(IsForeground());
#endif
Contract.ThrowIfFalse(IsForeground());
}
......@@ -107,7 +156,7 @@ public Task InvokeBelowInputPriority(Action action, CancellationToken cancellati
}
else
{
return Task.Factory.SafeStartNew(action, cancellationToken, ForegroundThreadAffinitizedObject.s_foregroundTaskScheduler);
return Task.Factory.SafeStartNew(action, cancellationToken, ForegroundTaskScheduler);
}
}
......
......@@ -66,9 +66,9 @@ public TestWorkspace(ExportProvider exportProvider, string workspaceKind = null,
/// Reset the thread affinity, in particular the designated foreground thread, to the active
/// thread.
/// </summary>
public static void ResetThreadAffinity()
internal static void ResetThreadAffinity(ForegroundThreadData foregroundThreadData = null)
{
ForegroundThreadAffinitizedObject.Initialize(force: true);
foregroundThreadData = foregroundThreadData ?? ForegroundThreadAffinitizedObject.DefaultForegroundThreadData;
// HACK: When the platform team took over several of our components they created a copy
// of ForegroundThreadAffinitizedObject. This needs to be reset in the same way as our copy
......@@ -78,8 +78,8 @@ public static void ResetThreadAffinity()
var type = assembly.GetType("Microsoft.VisualStudio.Language.Intellisense.Implementation.ForegroundThreadAffinitizedObject", throwOnError: false);
if (type != null)
{
type.GetField("foregroundThread", BindingFlags.Static | BindingFlags.NonPublic).SetValue(null, ForegroundThreadAffinitizedObject.ForegroundThread);
type.GetField("ForegroundTaskScheduler", BindingFlags.Static | BindingFlags.NonPublic).SetValue(null, ForegroundThreadAffinitizedObject.ForegroundTaskScheduler);
type.GetField("foregroundThread", BindingFlags.Static | BindingFlags.NonPublic).SetValue(null, foregroundThreadData.Thread);
type.GetField("ForegroundTaskScheduler", BindingFlags.Static | BindingFlags.NonPublic).SetValue(null, foregroundThreadData.TaskScheduler);
break;
}
......
......@@ -40,6 +40,10 @@
<Project>{f7712928-1175-47b3-8819-ee086753dee2}</Project>
<Name>TestUtilities.FX45</Name>
</ProjectReference>
<ProjectReference Include="..\Core\EditorFeatures.csproj">
<Project>{3CDEEAB7-2256-418A-BEB2-620B5CB16302}</Project>
<Name>EditorFeatures</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Reference Include="PresentationCore" />
......
......@@ -4,6 +4,7 @@
using System.Windows;
using System.Windows.Threading;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Xunit.Abstractions;
using Xunit.Sdk;
......@@ -27,6 +28,12 @@ public override Task<RunSummary> RunAsync(IMessageSink diagnosticMessageSink, IM
try
{
// Sync up FTAO to the context that we are creating here.
ForegroundThreadAffinitizedObject.DefaultForegroundThreadData = new ForegroundThreadData(
Thread.CurrentThread,
StaTaskScheduler.DefaultSta,
ForegroundThreadDataKind.StaUnitTest);
// All WPF Tests need a DispatcherSynchronizationContext and we dont want to block pending keyboard
// or mouse input from the user. So use background priority which is a single level below user input.
var dispatcherSynchronizationContext = new DispatcherSynchronizationContext();
......@@ -46,6 +53,8 @@ public override Task<RunSummary> RunAsync(IMessageSink diagnosticMessageSink, IM
}
finally
{
ForegroundThreadAffinitizedObject.DefaultForegroundThreadData = null;
// Cleanup the synchronization context even if the test is failing exceptionally
SynchronizationContext.SetSynchronizationContext(null);
}
......
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports System.Xml.Linq
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.Editor.Commands
......@@ -1058,7 +1056,7 @@ End Module
End Sub
<WorkItem(3119, "https://github.com/dotnet/roslyn/issues/3119")>
<Fact, Trait(Traits.Feature, Traits.Features.LineCommit)>
<WpfFact, Trait(Traits.Feature, Traits.Features.LineCommit)>
Public Sub MissingThenInIf()
Using testData = New CommitTestData(
<Workspace>
......@@ -1093,7 +1091,7 @@ End Class
End Sub
<WorkItem(3119, "https://github.com/dotnet/roslyn/issues/3119")>
<Fact, Trait(Traits.Feature, Traits.Features.LineCommit)>
<WpfFact, Trait(Traits.Feature, Traits.Features.LineCommit)>
Public Sub MissingThenInElseIf()
Using testData = New CommitTestData(
<Workspace>
......
......@@ -29,7 +29,9 @@ protected override void Initialize()
{
base.Initialize();
_foregroundObject = ForegroundThreadAffinitizedObject.Initialize();
var defaultForegroundThreadData = ForegroundThreadData.CreateDefault();
ForegroundThreadAffinitizedObject.DefaultForegroundThreadData = defaultForegroundThreadData;
_foregroundObject = new ForegroundThreadAffinitizedObject(defaultForegroundThreadData);
foreach (var editorFactory in CreateEditorFactories())
{
......
......@@ -218,7 +218,7 @@ private void SetUserPreferences()
}
else
{
Task.Factory.StartNew(this.SetUserPreferences, CancellationToken.None, TaskCreationOptions.None, ForegroundThreadAffinitizedObject.ForegroundTaskScheduler);
Task.Factory.StartNew(this.SetUserPreferences, CancellationToken.None, TaskCreationOptions.None, ForegroundThreadAffinitizedObject.DefaultForegroundThreadData.TaskScheduler);
}
}
......
......@@ -2,6 +2,7 @@
using System;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using Microsoft.CodeAnalysis;
......@@ -40,7 +41,8 @@ protected override void Initialize()
{
base.Initialize();
ForegroundThreadAffinitizedObject.Initialize();
ForegroundThreadAffinitizedObject.DefaultForegroundThreadData = ForegroundThreadData.CreateDefault();
Debug.Assert(ForegroundThreadAffinitizedObject.DefaultForegroundThreadData.Kind == ForegroundThreadDataKind.Wpf);
FatalError.Handler = FailFast.OnFatalException;
FatalError.NonFatalHandler = WatsonReporter.Report;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册