Add foreground dispatcher

This commit is contained in:
Ryan Nowak 2017-08-24 07:58:18 -07:00
parent d87e0f7fbd
commit a9a86fa3bf
7 changed files with 120 additions and 48 deletions

View File

@ -1,34 +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;
using System.Threading;
namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
{
internal class ForegroundThreadAffinitizedObject
{
private readonly Thread _foregroundThread;
public ForegroundThreadAffinitizedObject()
{
_foregroundThread = Thread.CurrentThread;
}
public void AssertIsForeground()
{
if (Thread.CurrentThread != _foregroundThread)
{
throw new InvalidOperationException("Expected to be on the foreground thread and was not.");
}
}
public void AssertIsBackground()
{
if (Thread.CurrentThread == _foregroundThread)
{
throw new InvalidOperationException("Expected to be on a background thread and was not.");
}
}
}
}

View File

@ -22,11 +22,16 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
private const int IdleDelay = 3000; private const int IdleDelay = 3000;
private readonly ICompletionBroker _completionBroker; private readonly ICompletionBroker _completionBroker;
private readonly BackgroundParser _parser; private readonly BackgroundParser _parser;
private readonly ForegroundThreadAffinitizedObject _foregroundThreadAffinitizedObject; private readonly ForegroundDispatcher _dispatcher;
private RazorSyntaxTreePartialParser _partialParser; private RazorSyntaxTreePartialParser _partialParser;
public VisualStudioRazorParser(ITextBuffer buffer, RazorTemplateEngine templateEngine, string filePath, ICompletionBroker completionBroker) public VisualStudioRazorParser(ForegroundDispatcher dispatcher, ITextBuffer buffer, RazorTemplateEngine templateEngine, string filePath, ICompletionBroker completionBroker)
{ {
if (dispatcher == null)
{
throw new ArgumentNullException(nameof(dispatcher));
}
if (buffer == null) if (buffer == null)
{ {
throw new ArgumentNullException(nameof(buffer)); throw new ArgumentNullException(nameof(buffer));
@ -47,6 +52,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
throw new ArgumentNullException(nameof(completionBroker)); throw new ArgumentNullException(nameof(completionBroker));
} }
_dispatcher = dispatcher;
TemplateEngine = templateEngine; TemplateEngine = templateEngine;
FilePath = filePath; FilePath = filePath;
_textBuffer = buffer; _textBuffer = buffer;
@ -56,7 +62,6 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
_idleTimer = new Timer(IdleDelay); _idleTimer = new Timer(IdleDelay);
_idleTimer.Elapsed += Onidle; _idleTimer.Elapsed += Onidle;
_parser.ResultsReady += OnResultsReady; _parser.ResultsReady += OnResultsReady;
_foregroundThreadAffinitizedObject = new ForegroundThreadAffinitizedObject();
_parser.Start(); _parser.Start();
} }
@ -80,7 +85,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
public void Dispose() public void Dispose()
{ {
_foregroundThreadAffinitizedObject.AssertIsForeground(); _dispatcher.AssertForegroundThread();
_textBuffer.Changed -= TextBuffer_OnChanged; _textBuffer.Changed -= TextBuffer_OnChanged;
_parser.Dispose(); _parser.Dispose();
@ -89,7 +94,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
private void TextBuffer_OnChanged(object sender, TextContentChangedEventArgs contentChange) private void TextBuffer_OnChanged(object sender, TextContentChangedEventArgs contentChange)
{ {
_foregroundThreadAffinitizedObject.AssertIsForeground(); _dispatcher.AssertForegroundThread();
if (contentChange.Changes.Count > 0) if (contentChange.Changes.Count > 0)
{ {
@ -144,7 +149,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
private void Onidle(object sender, ElapsedEventArgs e) private void Onidle(object sender, ElapsedEventArgs e)
{ {
_foregroundThreadAffinitizedObject.AssertIsBackground(); _dispatcher.AssertBackgroundThread();
var textViews = Array.Empty<ITextView>(); var textViews = Array.Empty<ITextView>();
@ -162,7 +167,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
private void OnResultsReady(object sender, DocumentStructureChangedEventArgs args) private void OnResultsReady(object sender, DocumentStructureChangedEventArgs args)
{ {
_foregroundThreadAffinitizedObject.AssertIsBackground(); _dispatcher.AssertBackgroundThread();
if (DocumentStructureChanged != null) if (DocumentStructureChanged != null)
{ {

View File

@ -0,0 +1,31 @@
// 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.Runtime.CompilerServices;
namespace Microsoft.VisualStudio.LanguageServices.Razor
{
internal abstract class ForegroundDispatcher
{
public abstract bool IsForegroundThread { get; }
public virtual void AssertForegroundThread([CallerMemberName] string caller = null)
{
if (!IsForegroundThread)
{
caller = caller == null ? Resources.ForegroundDispatcher_NoMethodNamePlaceholder : $"'{caller}'";
throw new InvalidOperationException(Resources.FormatForegroundDispatcher_AssertForegroundThread(caller));
}
}
public virtual void AssertBackgroundThread([CallerMemberName] string caller = null)
{
if (IsForegroundThread)
{
caller = caller == null ? Resources.ForegroundDispatcher_NoMethodNamePlaceholder : $"'{caller}'";
throw new InvalidOperationException(Resources.FormatForegroundDispatcher_AssertBackgroundThread(caller));
}
}
}
}

View File

@ -24,6 +24,48 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
internal static string FormatArgumentCannotBeNullOrEmpty() internal static string FormatArgumentCannotBeNullOrEmpty()
=> GetString("ArgumentCannotBeNullOrEmpty"); => GetString("ArgumentCannotBeNullOrEmpty");
/// <summary>
/// {0} must be called on a background thread.
/// </summary>
internal static string ForegroundDispatcher_AssertBackgroundThread
{
get => GetString("ForegroundDispatcher_AssertBackgroundThread");
}
/// <summary>
/// {0} must be called on a background thread.
/// </summary>
internal static string FormatForegroundDispatcher_AssertBackgroundThread(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("ForegroundDispatcher_AssertBackgroundThread"), p0);
/// <summary>
/// {0} must be called on the foreground thread.
/// </summary>
internal static string ForegroundDispatcher_AssertForegroundThread
{
get => GetString("ForegroundDispatcher_AssertForegroundThread");
}
/// <summary>
/// {0} must be called on the foreground thread.
/// </summary>
internal static string FormatForegroundDispatcher_AssertForegroundThread(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("ForegroundDispatcher_AssertForegroundThread"), p0);
/// <summary>
/// The method
/// </summary>
internal static string ForegroundDispatcher_NoMethodNamePlaceholder
{
get => GetString("ForegroundDispatcher_NoMethodNamePlaceholder");
}
/// <summary>
/// The method
/// </summary>
internal static string FormatForegroundDispatcher_NoMethodNamePlaceholder()
=> GetString("ForegroundDispatcher_NoMethodNamePlaceholder");
/// <summary> /// <summary>
/// An unexpected exception occurred when invoking '{0}.{1}' on the Razor language service. /// An unexpected exception occurred when invoking '{0}.{1}' on the Razor language service.
/// </summary> /// </summary>

View File

@ -120,6 +120,15 @@
<data name="ArgumentCannotBeNullOrEmpty" xml:space="preserve"> <data name="ArgumentCannotBeNullOrEmpty" xml:space="preserve">
<value>Value cannot be null or an empty string.</value> <value>Value cannot be null or an empty string.</value>
</data> </data>
<data name="ForegroundDispatcher_AssertBackgroundThread" xml:space="preserve">
<value>{0} must be called on a background thread.</value>
</data>
<data name="ForegroundDispatcher_AssertForegroundThread" xml:space="preserve">
<value>{0} must be called on the foreground thread.</value>
</data>
<data name="ForegroundDispatcher_NoMethodNamePlaceholder" xml:space="preserve">
<value>The method</value>
</data>
<data name="UnexpectedException" xml:space="preserve"> <data name="UnexpectedException" xml:space="preserve">
<value>An unexpected exception occurred when invoking '{0}.{1}' on the Razor language service.</value> <value>An unexpected exception occurred when invoking '{0}.{1}' on the Razor language service.</value>
</data> </data>

View File

@ -18,20 +18,20 @@ using Xunit;
namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
{ {
public class VisualStudioRazorParserTest public class VisualStudioRazorParserTest : ForegroundDispatcherTestBase
{ {
private const string TestLinePragmaFileName = "C:\\This\\Path\\Is\\Just\\For\\Line\\Pragmas.cshtml"; private const string TestLinePragmaFileName = "C:\\This\\Path\\Is\\Just\\For\\Line\\Pragmas.cshtml";
[Fact] [Fact]
public void ConstructorRequiresNonNullPhysicalPath() public void ConstructorRequiresNonNullPhysicalPath()
{ {
Assert.Throws<ArgumentException>("filePath", () => new VisualStudioRazorParser(new TestTextBuffer(null), CreateTemplateEngine(), null, new TestCompletionBroker())); Assert.Throws<ArgumentException>("filePath", () => new VisualStudioRazorParser(Dispatcher, new TestTextBuffer(null), CreateTemplateEngine(), null, new TestCompletionBroker()));
} }
[Fact] [Fact]
public void ConstructorRequiresNonEmptyPhysicalPath() public void ConstructorRequiresNonEmptyPhysicalPath()
{ {
Assert.Throws<ArgumentException>("filePath", () => new VisualStudioRazorParser(new TestTextBuffer(null), CreateTemplateEngine(), string.Empty, new TestCompletionBroker())); Assert.Throws<ArgumentException>("filePath", () => new VisualStudioRazorParser(Dispatcher, new TestTextBuffer(null), CreateTemplateEngine(), string.Empty, new TestCompletionBroker()));
} }
[Fact] [Fact]
@ -40,7 +40,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
// Arrange // Arrange
var original = new StringTextSnapshot("Foo @bar Baz"); var original = new StringTextSnapshot("Foo @bar Baz");
var testBuffer = new TestTextBuffer(original); var testBuffer = new TestTextBuffer(original);
using (var parser = new VisualStudioRazorParser(testBuffer, CreateTemplateEngine(), TestLinePragmaFileName, new TestCompletionBroker())) using (var parser = new VisualStudioRazorParser(Dispatcher, testBuffer, CreateTemplateEngine(), TestLinePragmaFileName, new TestCompletionBroker()))
{ {
parser._idleTimer.Interval = 100; parser._idleTimer.Interval = 100;
var changed = new StringTextSnapshot("Foo @bap Daz"); var changed = new StringTextSnapshot("Foo @bap Daz");
@ -519,9 +519,9 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
RunTypeKeywordTest("class"); RunTypeKeywordTest("class");
} }
private static TestParserManager CreateParserManager(ITextSnapshot originalSnapshot) private TestParserManager CreateParserManager(ITextSnapshot originalSnapshot, int idleDelay = 50)
{ {
var parser = new VisualStudioRazorParser(new TestTextBuffer(originalSnapshot), CreateTemplateEngine(), TestLinePragmaFileName, new TestCompletionBroker()); var parser = new VisualStudioRazorParser(Dispatcher, new TestTextBuffer(originalSnapshot), CreateTemplateEngine(), TestLinePragmaFileName, new TestCompletionBroker());
return new TestParserManager(parser); return new TestParserManager(parser);
} }
@ -552,7 +552,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
return templateEngine; return templateEngine;
} }
private static void RunTypeKeywordTest(string keyword) private void RunTypeKeywordTest(string keyword)
{ {
// Arrange // Arrange
var before = "@" + keyword.Substring(0, keyword.Length - 1); var before = "@" + keyword.Substring(0, keyword.Length - 1);

View File

@ -0,0 +1,19 @@
// 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;
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;
}
}
}