Add TaskSchedulers to the dispatcher abstraction
This commit is contained in:
parent
c94c110de3
commit
d8431067a5
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.CodeAnalysis.Host;
|
using Microsoft.CodeAnalysis.Host;
|
||||||
|
|
||||||
namespace Microsoft.CodeAnalysis.Razor
|
namespace Microsoft.CodeAnalysis.Razor
|
||||||
|
|
@ -11,6 +12,10 @@ namespace Microsoft.CodeAnalysis.Razor
|
||||||
{
|
{
|
||||||
public abstract bool IsForegroundThread { get; }
|
public abstract bool IsForegroundThread { get; }
|
||||||
|
|
||||||
|
public abstract TaskScheduler ForegroundScheduler { get; }
|
||||||
|
|
||||||
|
public abstract TaskScheduler BackgroundScheduler { get; }
|
||||||
|
|
||||||
public virtual void AssertForegroundThread([CallerMemberName] string caller = null)
|
public virtual void AssertForegroundThread([CallerMemberName] string caller = null)
|
||||||
{
|
{
|
||||||
if (!IsForegroundThread)
|
if (!IsForegroundThread)
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System.Composition;
|
using System.Composition;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.CodeAnalysis.Host.Mef;
|
using Microsoft.CodeAnalysis.Host.Mef;
|
||||||
using Microsoft.CodeAnalysis.Razor;
|
using Microsoft.CodeAnalysis.Razor;
|
||||||
using Microsoft.VisualStudio.Shell;
|
using Microsoft.VisualStudio.Shell;
|
||||||
|
|
@ -12,6 +13,10 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
|
||||||
[ExportWorkspaceService(typeof(ForegroundDispatcher), ServiceLayer.Host)]
|
[ExportWorkspaceService(typeof(ForegroundDispatcher), ServiceLayer.Host)]
|
||||||
internal class VisualStudioForegroundDispatcher : ForegroundDispatcher
|
internal class VisualStudioForegroundDispatcher : ForegroundDispatcher
|
||||||
{
|
{
|
||||||
|
public override TaskScheduler BackgroundScheduler { get; } = TaskScheduler.Default;
|
||||||
|
|
||||||
|
public override TaskScheduler ForegroundScheduler { get; } = TaskScheduler.FromCurrentSynchronizationContext();
|
||||||
|
|
||||||
public override bool IsForegroundThread => ThreadHelper.CheckAccess();
|
public override bool IsForegroundThread => ThreadHelper.CheckAccess();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -13,6 +13,10 @@
|
||||||
<None Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
|
<None Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="WindowsBase" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\src\Microsoft.VisualStudio.LanguageServices.Razor\Microsoft.VisualStudio.LanguageServices.Razor.csproj" />
|
<ProjectReference Include="..\..\src\Microsoft.VisualStudio.LanguageServices.Razor\Microsoft.VisualStudio.LanguageServices.Razor.csproj" />
|
||||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Razor.Runtime\Microsoft.AspNetCore.Razor.Runtime.csproj" />
|
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Razor.Runtime\Microsoft.AspNetCore.Razor.Runtime.csproj" />
|
||||||
|
|
|
||||||
|
|
@ -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]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue