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 readonly ICompletionBroker _completionBroker;
private readonly BackgroundParser _parser;
private readonly ForegroundThreadAffinitizedObject _foregroundThreadAffinitizedObject;
private readonly ForegroundDispatcher _dispatcher;
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)
{
throw new ArgumentNullException(nameof(buffer));
@ -47,6 +52,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
throw new ArgumentNullException(nameof(completionBroker));
}
_dispatcher = dispatcher;
TemplateEngine = templateEngine;
FilePath = filePath;
_textBuffer = buffer;
@ -56,7 +62,6 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
_idleTimer = new Timer(IdleDelay);
_idleTimer.Elapsed += Onidle;
_parser.ResultsReady += OnResultsReady;
_foregroundThreadAffinitizedObject = new ForegroundThreadAffinitizedObject();
_parser.Start();
}
@ -80,7 +85,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
public void Dispose()
{
_foregroundThreadAffinitizedObject.AssertIsForeground();
_dispatcher.AssertForegroundThread();
_textBuffer.Changed -= TextBuffer_OnChanged;
_parser.Dispose();
@ -89,7 +94,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
private void TextBuffer_OnChanged(object sender, TextContentChangedEventArgs contentChange)
{
_foregroundThreadAffinitizedObject.AssertIsForeground();
_dispatcher.AssertForegroundThread();
if (contentChange.Changes.Count > 0)
{
@ -144,7 +149,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
private void Onidle(object sender, ElapsedEventArgs e)
{
_foregroundThreadAffinitizedObject.AssertIsBackground();
_dispatcher.AssertBackgroundThread();
var textViews = Array.Empty<ITextView>();
@ -162,7 +167,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
private void OnResultsReady(object sender, DocumentStructureChangedEventArgs args)
{
_foregroundThreadAffinitizedObject.AssertIsBackground();
_dispatcher.AssertBackgroundThread();
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()
=> 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>
/// An unexpected exception occurred when invoking '{0}.{1}' on the Razor language service.
/// </summary>

View File

@ -120,6 +120,15 @@
<data name="ArgumentCannotBeNullOrEmpty" xml:space="preserve">
<value>Value cannot be null or an empty string.</value>
</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">
<value>An unexpected exception occurred when invoking '{0}.{1}' on the Razor language service.</value>
</data>

View File

@ -18,20 +18,20 @@ using Xunit;
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";
[Fact]
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]
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]
@ -40,7 +40,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
// Arrange
var original = new StringTextSnapshot("Foo @bar Baz");
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;
var changed = new StringTextSnapshot("Foo @bap Daz");
@ -519,9 +519,9 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
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);
}
@ -552,7 +552,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
return templateEngine;
}
private static void RunTypeKeywordTest(string keyword)
private void RunTypeKeywordTest(string keyword)
{
// Arrange
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;
}
}
}