提交 8ee61d63 编写于 作者: C CyrusNajmabadi

Provide a synchronous path for outlining so it won't block hte UI thread...

Provide a synchronous path for outlining so it won't block hte UI thread waiting for a threadpool thread.
上级 71c56836
......@@ -10,7 +10,7 @@
namespace Microsoft.CodeAnalysis.Editor.Implementation.Outlining
{
internal abstract class AbstractOutliningService : IOutliningService
internal abstract class AbstractOutliningService : ISynchronousOutliningService
{
private readonly ImmutableDictionary<Type, ImmutableArray<AbstractSyntaxOutliner>> _nodeOutlinerMap;
private readonly ImmutableDictionary<int, ImmutableArray<AbstractSyntaxOutliner>> _triviaOutlinerMap;
......@@ -23,16 +23,40 @@ internal abstract class AbstractOutliningService : IOutliningService
_triviaOutlinerMap = defaultTriviaOutlinerMap;
}
public async Task<IList<OutliningSpan>> GetOutliningSpansAsync(Document document, CancellationToken cancellationToken)
/// <summary>
/// Keep in sync with <see cref="GetOutliningSpansAsync"/>
/// </summary>
public IList<OutliningSpan> GetOutliningSpans(
Document document, CancellationToken cancellationToken)
{
try
{
var syntaxDocument = await SyntacticDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false);
var syntaxRoot = document.GetSyntaxRootSynchronously(cancellationToken);
// change this to shared pool once RI
var regions = new List<OutliningSpan>();
RegionCollector.CollectOutliningSpans(syntaxDocument, _nodeOutlinerMap, _triviaOutlinerMap, regions, cancellationToken);
RegionCollector.CollectOutliningSpans(document, syntaxRoot, _nodeOutlinerMap, _triviaOutlinerMap, regions, cancellationToken);
return regions;
}
catch (Exception e) when (FatalError.ReportUnlessCanceled(e))
{
throw ExceptionUtilities.Unreachable;
}
}
/// <summary>
/// Keep in sync with <see cref="GetOutliningSpans"/>
/// </summary>
public async Task<IList<OutliningSpan>> GetOutliningSpansAsync(
Document document, CancellationToken cancellationToken)
{
try
{
var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
// change this to shared pool once RI
var regions = new List<OutliningSpan>();
RegionCollector.CollectOutliningSpans(document, syntaxRoot, _nodeOutlinerMap, _triviaOutlinerMap, regions, cancellationToken);
return regions;
}
catch (Exception e) when (FatalError.ReportUnlessCanceled(e))
......
......@@ -11,4 +11,9 @@ internal interface IOutliningService : ILanguageService
{
Task<IList<OutliningSpan>> GetOutliningSpansAsync(Document document, CancellationToken cancellationToken);
}
internal interface ISynchronousOutliningService : IOutliningService
{
IList<OutliningSpan> GetOutliningSpans(Document document, CancellationToken cancellationToken);
}
}
......@@ -92,49 +92,51 @@ protected override ITaggerEventSource CreateEventSource(ITextView textViewOpt, I
TaggerEventSources.OnWorkspaceRegistrationChanged(subjectBuffer, TaggerDelay.OnIdle));
}
protected override async Task ProduceTagsAsync(TaggerContext<IOutliningRegionTag> context, DocumentSnapshotSpan documentSnapshotSpan, int? caretPosition)
/// <summary>
/// Keep this in sync with <see cref="ProduceTagsSynchronously"/>
/// </summary>
protected override async Task ProduceTagsAsync(
TaggerContext<IOutliningRegionTag> context, DocumentSnapshotSpan documentSnapshotSpan, int? caretPosition)
{
try
{
var cancellationToken = context.CancellationToken;
using (Logger.LogBlock(FunctionId.Tagger_Outlining_TagProducer_ProduceTags, cancellationToken))
var outliningService = TryGetOutliningService(context, documentSnapshotSpan);
if (outliningService != null)
{
var regions = await outliningService.GetOutliningSpansAsync(
documentSnapshotSpan.Document, context.CancellationToken).ConfigureAwait(false);
ProcessOutliningSpans(context, documentSnapshotSpan.SnapshotSpan, outliningService, regions);
}
}
catch (Exception e) when (FatalError.ReportUnlessCanceled(e))
{
throw ExceptionUtilities.Unreachable;
}
}
/// <summary>
/// Keep this in sync with <see cref="ProduceTagsAsync"/>
/// </summary>
protected override void ProduceTagsSynchronously(
TaggerContext<IOutliningRegionTag> context, DocumentSnapshotSpan documentSnapshotSpan, int? caretPosition)
{
try
{
var outliningService = TryGetOutliningService(context, documentSnapshotSpan);
if (outliningService != null)
{
var document = documentSnapshotSpan.Document;
var snapshotSpan = documentSnapshotSpan.SnapshotSpan;
var snapshot = snapshotSpan.Snapshot;
var cancellationToken = context.CancellationToken;
if (document != null)
{
var outliningService = document.Project.LanguageServices.GetService<IOutliningService>();
if (outliningService != null)
{
var regions = await outliningService.GetOutliningSpansAsync(document, cancellationToken).ConfigureAwait(false);
if (regions != null)
{
regions = GetMultiLineRegions(outliningService, regions, snapshotSpan.Snapshot);
// Create the outlining tags.
var tagSpans =
from region in regions
let spanToCollapse = new SnapshotSpan(snapshot, region.TextSpan.ToSpan())
let hintSpan = new SnapshotSpan(snapshot, region.HintSpan.ToSpan())
let tag = new Tag(snapshot.TextBuffer,
region.BannerText,
hintSpan,
region.AutoCollapse,
region.IsDefaultCollapsed,
_textEditorFactoryService,
_projectionBufferFactoryService,
_editorOptionsFactoryService)
select new TagSpan<IOutliningRegionTag>(spanToCollapse, tag);
foreach (var tagSpan in tagSpans)
{
context.AddTag(tagSpan);
}
}
}
}
// Try to call through the synchronous service if possible. Otherwise, fallback
// and make a blocking call against the async service.
var synchronousOutliningService = outliningService as ISynchronousOutliningService;
var regions = synchronousOutliningService != null
? synchronousOutliningService.GetOutliningSpans(document, cancellationToken)
: outliningService.GetOutliningSpansAsync(document, cancellationToken).WaitAndGetResult(cancellationToken);
ProcessOutliningSpans(context, documentSnapshotSpan.SnapshotSpan, outliningService, regions);
}
}
catch (Exception e) when (FatalError.ReportUnlessCanceled(e))
......@@ -143,6 +145,56 @@ protected override async Task ProduceTagsAsync(TaggerContext<IOutliningRegionTag
}
}
private IOutliningService TryGetOutliningService(
TaggerContext<IOutliningRegionTag> context,
DocumentSnapshotSpan documentSnapshotSpan)
{
var cancellationToken = context.CancellationToken;
using (Logger.LogBlock(FunctionId.Tagger_Outlining_TagProducer_ProduceTags, cancellationToken))
{
var document = documentSnapshotSpan.Document;
var snapshotSpan = documentSnapshotSpan.SnapshotSpan;
var snapshot = snapshotSpan.Snapshot;
if (document != null)
{
return document.Project.LanguageServices.GetService<IOutliningService>();
}
}
return null;
}
private void ProcessOutliningSpans(
TaggerContext<IOutliningRegionTag> context, SnapshotSpan snapshotSpan, IOutliningService outliningService, IList<OutliningSpan> regions)
{
if (regions != null)
{
var snapshot = snapshotSpan.Snapshot;
regions = GetMultiLineRegions(outliningService, regions, snapshot);
// Create the outlining tags.
var tagSpans =
from region in regions
let spanToCollapse = new SnapshotSpan(snapshot, region.TextSpan.ToSpan())
let hintSpan = new SnapshotSpan(snapshot, region.HintSpan.ToSpan())
let tag = new Tag(snapshot.TextBuffer,
region.BannerText,
hintSpan,
region.AutoCollapse,
region.IsDefaultCollapsed,
_textEditorFactoryService,
_projectionBufferFactoryService,
_editorOptionsFactoryService)
select new TagSpan<IOutliningRegionTag>(spanToCollapse, tag);
foreach (var tagSpan in tagSpans)
{
context.AddTag(tagSpan);
}
}
}
private static bool s_exceptionReported = false;
private IList<OutliningSpan> GetMultiLineRegions(IOutliningService service, IList<OutliningSpan> regions, ITextSnapshot snapshot)
......
......@@ -9,14 +9,14 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.Outlining
{
internal class RegionCollector
{
private readonly SyntacticDocument _document;
private readonly Document _document;
private readonly ImmutableDictionary<Type, ImmutableArray<AbstractSyntaxOutliner>> _nodeOutlinerMap;
private readonly ImmutableDictionary<int, ImmutableArray<AbstractSyntaxOutliner>> _triviaOutlinerMap;
private readonly List<OutliningSpan> _regions;
private readonly CancellationToken _cancellationToken;
private RegionCollector(
SyntacticDocument document,
Document document,
ImmutableDictionary<Type, ImmutableArray<AbstractSyntaxOutliner>> nodeOutlinerMap,
ImmutableDictionary<int, ImmutableArray<AbstractSyntaxOutliner>> triviaOutlinerMap,
List<OutliningSpan> spans,
......@@ -30,14 +30,15 @@ internal class RegionCollector
}
public static void CollectOutliningSpans(
SyntacticDocument document,
Document document,
SyntaxNode syntaxRoot,
ImmutableDictionary<Type, ImmutableArray<AbstractSyntaxOutliner>> nodeOutlinerMap,
ImmutableDictionary<int, ImmutableArray<AbstractSyntaxOutliner>> triviaOutlinerMap,
List<OutliningSpan> spans,
CancellationToken cancellationToken)
{
var collector = new RegionCollector(document, nodeOutlinerMap, triviaOutlinerMap, spans, cancellationToken);
collector.Collect(document.Root);
collector.Collect(syntaxRoot);
}
private void Collect(SyntaxNode root)
......@@ -66,7 +67,7 @@ private void GetOutliningSpans(SyntaxNode node)
{
_cancellationToken.ThrowIfCancellationRequested();
outliner.CollectOutliningSpans(_document.Document, node, _regions, _cancellationToken);
outliner.CollectOutliningSpans(_document, node, _regions, _cancellationToken);
}
}
}
......@@ -90,7 +91,7 @@ private void GetOutliningSpans(SyntaxTriviaList triviaList)
{
_cancellationToken.ThrowIfCancellationRequested();
outliner.CollectOutliningSpans(_document.Document, trivia, _regions, _cancellationToken);
outliner.CollectOutliningSpans(_document, trivia, _regions, _cancellationToken);
}
}
}
......
......@@ -556,13 +556,18 @@ private IEnumerable<ITagSpan<TTag>> GetNonIntersectingTagSpans(IEnumerable<Snaps
ProcessContext(spansToTag, oldTagTrees, context);
}
private Task ProduceTagsAsync(TaggerContext<TTag> context)
private bool ShouldSkipTagProduction()
{
var options = _dataSource.Options ?? SpecializedCollections.EmptyEnumerable<Option<bool>>();
var perLanguageOptions = _dataSource.PerLanguageOptions ?? SpecializedCollections.EmptyEnumerable<PerLanguageOption<bool>>();
if (options.Any(option => !_subjectBuffer.GetOption(option)) ||
perLanguageOptions.Any(option => !_subjectBuffer.GetOption(option)))
return options.Any(option => !_subjectBuffer.GetOption(option)) ||
perLanguageOptions.Any(option => !_subjectBuffer.GetOption(option));
}
private Task ProduceTagsAsync(TaggerContext<TTag> context)
{
if (ShouldSkipTagProduction())
{
// If the feature is disabled, then just produce no tags.
return SpecializedTasks.EmptyTask;
......@@ -571,6 +576,16 @@ private Task ProduceTagsAsync(TaggerContext<TTag> context)
return _dataSource.ProduceTagsAsync(context);
}
private void ProduceTagsSynchronously(TaggerContext<TTag> context)
{
if (ShouldSkipTagProduction())
{
return;
}
_dataSource.ProduceTagsSynchronously(context);
}
private void ProcessContext(
List<DocumentSnapshotSpan> spansToTag,
ImmutableDictionary<ITextBuffer, TagSpanIntervalTree<TTag>> oldTagTrees,
......@@ -724,7 +739,8 @@ public TagSpanIntervalTree<TTag> GetAccurateTagIntervalTreeForBuffer(ITextBuffer
var context = new TaggerContext<TTag>(
this.State, spansToTag, caretPoint, this.AccumulatedTextChanges, oldTagTrees, cancellationToken);
ProduceTagsAsync(context).Wait(cancellationToken);
ProduceTagsSynchronously(context);
ProcessContext(spansToTag, oldTagTrees, context);
}
......
......@@ -202,13 +202,31 @@ internal Task ProduceTagsAsync_ForTestingPurposesOnly(TaggerContext<TTag> contex
/// <summary>
/// Produce tags for the given context.
/// Keep in sync with <see cref="ProduceTagsSynchronously(TaggerContext{TTag})"/>
/// </summary>
protected virtual async Task ProduceTagsAsync(TaggerContext<TTag> context)
{
foreach (var spanToTag in context.SpansToTag)
{
context.CancellationToken.ThrowIfCancellationRequested();
await ProduceTagsAsync(context, spanToTag, GetCaretPosition(context.CaretPosition, spanToTag.SnapshotSpan)).ConfigureAwait(false);
await ProduceTagsAsync(
context, spanToTag,
GetCaretPosition(context.CaretPosition, spanToTag.SnapshotSpan)).ConfigureAwait(false);
}
}
/// <summary>
/// Produce tags for the given context.
/// Keep in sync with <see cref="ProduceTagsAsync(TaggerContext{TTag})"/>
/// </summary>
protected void ProduceTagsSynchronously(TaggerContext<TTag> context)
{
foreach (var spanToTag in context.SpansToTag)
{
context.CancellationToken.ThrowIfCancellationRequested();
ProduceTagsSynchronously(
context, spanToTag,
GetCaretPosition(context.CaretPosition, spanToTag.SnapshotSpan));
}
}
......@@ -223,6 +241,21 @@ protected virtual Task ProduceTagsAsync(TaggerContext<TTag> context, DocumentSna
return SpecializedTasks.EmptyTask;
}
protected virtual void ProduceTagsSynchronously(TaggerContext<TTag> context, DocumentSnapshotSpan spanToTag, int? caretPosition)
{
// By default we implement the sync version of this by blocking on the async version.
//
// The benefit of this is that all taggers can implicitly be used as IAccurateTaggers
// without any code changes.
//
// However, the drawback is that it means the UI thread might be blocked waiting for
// tasks to be scheduled and run on the threadpool.
//
// Taggers that need to be called accurately should override this method to produce
// results quickly if possible.
ProduceTagsAsync(context, spanToTag, caretPosition).Wait(context.CancellationToken);
}
private struct DiffResult
{
public NormalizedSnapshotSpanCollection Added { get; }
......
......@@ -154,18 +154,8 @@ public bool SupportsSemanticModel
}
}
/// <summary>
/// Gets the <see cref="SyntaxTree" /> for this document asynchronously.
/// </summary>
public Task<SyntaxTree> GetSyntaxTreeAsync(CancellationToken cancellationToken = default(CancellationToken))
private Task<SyntaxTree> GetExistingSyntaxTreeTask()
{
// If the language doesn't support getting syntax trees for a document, then bail out
// immediately.
if (!this.SupportsSyntaxTree)
{
return SpecializedTasks.Default<SyntaxTree>();
}
if (_syntaxTreeResultTask != null)
{
return _syntaxTreeResultTask;
......@@ -195,11 +185,51 @@ public Task<SyntaxTree> GetSyntaxTreeAsync(CancellationToken cancellationToken =
return _syntaxTreeResultTask;
}
return null;
}
/// <summary>
/// Gets the <see cref="SyntaxTree" /> for this document asynchronously.
/// </summary>
public Task<SyntaxTree> GetSyntaxTreeAsync(CancellationToken cancellationToken = default(CancellationToken))
{
// If the language doesn't support getting syntax trees for a document, then bail out
// immediately.
if (!this.SupportsSyntaxTree)
{
return SpecializedTasks.Default<SyntaxTree>();
}
var syntaxTreeTask = GetExistingSyntaxTreeTask();
if (syntaxTreeTask != null)
{
return syntaxTreeTask;
}
// we can't cache this result, since internally it uses AsyncLazy which
// care about cancellation token
return _state.GetSyntaxTreeAsync(cancellationToken);
}
private SyntaxTree GetSyntaxTree(CancellationToken cancellationToken)
{
if (!this.SupportsSyntaxTree)
{
return null;
}
// if we already have a stask for getting this syntax tree, and the task
// has completed, then we can just return that value.
var syntaxTreeTask = GetExistingSyntaxTreeTask();
if (syntaxTreeTask?.Status == TaskStatus.RanToCompletion)
{
return syntaxTreeTask.Result;
}
// Otherwise defer to our state to get this value.
return _state.GetSyntaxTree(cancellationToken);
}
/// <summary>
/// Gets the root node of the current syntax tree if the syntax tree has already been parsed and the tree is still cached.
/// In almost all cases, you should call <see cref="GetSyntaxRootAsync"/> to fetch the root node, which will parse
......@@ -226,6 +256,22 @@ public async Task<SyntaxNode> GetSyntaxRootAsync(CancellationToken cancellationT
return await tree.GetRootAsync(cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// Only for features that absolutely must run synchronously (probably because they're
/// on the UI thread). Right now, the only feature this is for is Outlining as VS will
/// block on that feature from the UI thread when a document is opened.
/// </summary>
internal SyntaxNode GetSyntaxRootSynchronously(CancellationToken cancellationToken)
{
if (!this.SupportsSyntaxTree)
{
return null;
}
var tree = this.GetSyntaxTree(cancellationToken);
return tree.GetRoot(cancellationToken);
}
/// <summary>
/// Gets the current semantic model for this document if the model is already computed and still cached.
/// In almost all cases, you should call <see cref="GetSemanticModelAsync"/>, which will compute the semantic model
......
......@@ -559,6 +559,15 @@ public async Task<SyntaxTree> GetSyntaxTreeAsync(CancellationToken cancellationT
return treeAndVersion.Tree;
}
internal SyntaxTree GetSyntaxTree(CancellationToken cancellationToken)
{
var treeAndVersion = _treeSource.GetValue(cancellationToken);
// make sure there is an association between this tree and this doc id before handing it out
BindSyntaxTreeToId(treeAndVersion.Tree, this.Id);
return treeAndVersion.Tree;
}
public bool TryGetTopLevelChangeTextVersion(out VersionStamp version)
{
TreeAndVersion treeAndVersion;
......
// Copyright (c) Microsoft. 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;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis
{
......@@ -24,16 +21,14 @@ protected SyntacticDocument(Document document, SourceText text, SyntaxTree tree,
this.Root = root;
}
public Project Project
{
get { return this.Document.Project; }
}
public Project Project => this.Document.Project;
public static async Task<SyntacticDocument> CreateAsync(Document document, CancellationToken cancellationToken)
public static async Task<SyntacticDocument> CreateAsync(
Document document, CancellationToken cancellationToken)
{
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
return new SyntacticDocument(document, text, root.SyntaxTree, root);
}
}
}
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册