Add foreground dispatcher
This commit is contained in:
parent
d87e0f7fbd
commit
a9a86fa3bf
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue