Add TaskSchedulers to the dispatcher abstraction

This commit is contained in:
Ryan Nowak 2017-08-29 21:36:45 -07:00
parent c94c110de3
commit d8431067a5
7 changed files with 199 additions and 20 deletions

View File

@ -3,6 +3,7 @@
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
namespace Microsoft.CodeAnalysis.Razor
@ -11,6 +12,10 @@ namespace Microsoft.CodeAnalysis.Razor
{
public abstract bool IsForegroundThread { get; }
public abstract TaskScheduler ForegroundScheduler { get; }
public abstract TaskScheduler BackgroundScheduler { get; }
public virtual void AssertForegroundThread([CallerMemberName] string caller = null)
{
if (!IsForegroundThread)

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Composition;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.VisualStudio.Shell;
@ -12,6 +13,10 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
[ExportWorkspaceService(typeof(ForegroundDispatcher), ServiceLayer.Host)]
internal class VisualStudioForegroundDispatcher : ForegroundDispatcher
{
public override TaskScheduler BackgroundScheduler { get; } = TaskScheduler.Default;
public override TaskScheduler ForegroundScheduler { get; } = TaskScheduler.FromCurrentSynchronizationContext();
public override bool IsForegroundThread => ThreadHelper.CheckAccess();
}
}

View File

@ -1,20 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading;
using Microsoft.CodeAnalysis.Razor;
namespace Microsoft.VisualStudio.LanguageServices.Razor
{
public abstract class ForegroundDispatcherTestBase
{
internal ForegroundDispatcher Dispatcher { get; } = new SingleThreadedForegroundDispatcher();
private class SingleThreadedForegroundDispatcher : ForegroundDispatcher
{
private Thread Thread { get; } = Thread.CurrentThread;
public override bool IsForegroundThread => Thread.CurrentThread == Thread;
}
}
}

View File

@ -13,6 +13,10 @@
<None Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.VisualStudio.LanguageServices.Razor\Microsoft.VisualStudio.LanguageServices.Razor.csproj" />
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Razor.Runtime\Microsoft.AspNetCore.Razor.Runtime.csproj" />

View File

@ -0,0 +1,52 @@
// Copyright (c) .NET Foundation. 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.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Razor;
namespace Xunit
{
public abstract class ForegroundDispatcherTestBase
{
internal ForegroundDispatcher Dispatcher { get; } = new SingleThreadedForegroundDispatcher();
private class SingleThreadedForegroundDispatcher : ForegroundDispatcher
{
public SingleThreadedForegroundDispatcher()
{
ForegroundScheduler = SynchronizationContext.Current == null ? new ThrowingTaskScheduler() : TaskScheduler.FromCurrentSynchronizationContext();
BackgroundScheduler = TaskScheduler.Default;
}
public override TaskScheduler ForegroundScheduler { get; }
public override TaskScheduler BackgroundScheduler { get; }
private Thread Thread { get; } = Thread.CurrentThread;
public override bool IsForegroundThread => Thread.CurrentThread == Thread;
}
private class ThrowingTaskScheduler : TaskScheduler
{
protected override IEnumerable<Task> GetScheduledTasks()
{
return Enumerable.Empty<Task>();
}
protected override void QueueTask(Task task)
{
throw new InvalidOperationException("Use [ForegroundFactAttribute]");
}
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
throw new InvalidOperationException("Use [ForegroundFactAttribute]");
}
}
}
}

View File

@ -0,0 +1,16 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Xunit;
using Xunit.Sdk;
namespace Microsoft.VisualStudio.LanguageServices.Razor.Test
{
// Similar to WpfFactAttribute https://github.com/xunit/samples.xunit/blob/969d9f7e887836f01a6c525324bf3db55658c28f/STAExamples/WpfFactAttribute.cs
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
[XunitTestCaseDiscoverer(nameof(ForegroundFactAttribute), nameof(Microsoft.VisualStudio.LanguageServices.Razor))]
internal class ForegroundFactAttribute : FactAttribute
{
}
}

View File

@ -0,0 +1,117 @@
// Copyright (c) .NET Foundation. 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.Collections.Generic;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Xunit
{
internal class ForegroundFactTestCase : LongLivedMarshalByRefObject, IXunitTestCase
{
private IXunitTestCase _inner;
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Called by the de-serializer", error: true)]
public ForegroundFactTestCase()
{
}
public ForegroundFactTestCase(IXunitTestCase testCase)
{
_inner = testCase;
}
public string DisplayName => _inner.DisplayName;
public IMethodInfo Method => _inner.Method;
public string SkipReason => _inner.SkipReason;
public ISourceInformation SourceInformation
{
get => _inner.SourceInformation;
set => _inner.SourceInformation = value;
}
public ITestMethod TestMethod => _inner.TestMethod;
public object[] TestMethodArguments => _inner.TestMethodArguments;
public Dictionary<string, List<string>> Traits => _inner.Traits;
public string UniqueID => _inner.UniqueID;
public void Deserialize(IXunitSerializationInfo info)
{
_inner = info.GetValue<IXunitTestCase>("InnerTestCase");
}
public void Serialize(IXunitSerializationInfo info)
{
info.AddValue("InnerTestCase", _inner);
}
public Task<RunSummary> RunAsync(
IMessageSink diagnosticMessageSink,
IMessageBus messageBus,
object[] constructorArguments,
ExceptionAggregator aggregator,
CancellationTokenSource cancellationTokenSource)
{
var tcs = new TaskCompletionSource<RunSummary>();
var thread = new Thread(() =>
{
try
{
SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext());
var worker = _inner.RunAsync(diagnosticMessageSink, messageBus, constructorArguments, aggregator, cancellationTokenSource);
Exception caught = null;
var frame = new DispatcherFrame();
Task.Run(async () =>
{
try
{
await worker;
}
catch (Exception ex)
{
caught = ex;
}
finally
{
frame.Continue = false;
}
});
Dispatcher.PushFrame(frame);
if (caught == null)
{
tcs.SetException(caught);
}
else
{
tcs.SetResult(worker.Result);
}
}
catch (Exception e)
{
tcs.SetException(e);
}
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
return tcs.Task;
}
}
}