Merge pull request #1089 from sharwell/handle-exception
Rewrite SyntaxVisualizerControl to catch exceptions
This commit is contained in:
Коммит
64fdcf16db
|
@ -0,0 +1,134 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
// NOTE: This code is derived from an implementation originally in dotnet/project-system:
|
||||
// https://github.com/dotnet/project-system/blob/bdf69d5420ec8d894f5bf4c3d4692900b7f2479c/src/Microsoft.VisualStudio.ProjectSystem.Managed/Threading/Tasks/CancellationSeries.cs
|
||||
//
|
||||
// See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the
|
||||
// reference implementation.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Roslyn.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Produces a series of <see cref="CancellationToken"/> objects such that requesting a new token
|
||||
/// causes the previously issued token to be cancelled.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Consuming code is responsible for managing overlapping asynchronous operations.</para>
|
||||
/// <para>This class has a lock-free implementation to minimise latency and contention.</para>
|
||||
/// </remarks>
|
||||
internal sealed class CancellationSeries : IDisposable
|
||||
{
|
||||
private CancellationTokenSource? _cts;
|
||||
|
||||
private readonly CancellationToken _superToken;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="CancellationSeries"/>.
|
||||
/// </summary>
|
||||
/// <param name="token">An optional cancellation token that, when cancelled, cancels the last
|
||||
/// issued token and causes any subsequent tokens to be issued in a cancelled state.</param>
|
||||
public CancellationSeries(CancellationToken token = default)
|
||||
{
|
||||
// Initialize with a pre-cancelled source to ensure HasActiveToken has the correct state
|
||||
_cts = new CancellationTokenSource();
|
||||
_cts.Cancel();
|
||||
|
||||
_superToken = token;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the cancellation series has an active token which has not been cancelled.
|
||||
/// </summary>
|
||||
public bool HasActiveToken
|
||||
=> _cts is { IsCancellationRequested: false };
|
||||
|
||||
/// <summary>
|
||||
/// Creates the next <see cref="CancellationToken"/> in the series, ensuring the last issued
|
||||
/// token (if any) is cancelled first.
|
||||
/// </summary>
|
||||
/// <param name="token">An optional cancellation token that, when cancelled, cancels the
|
||||
/// returned token.</param>
|
||||
/// <returns>
|
||||
/// A cancellation token that will be cancelled when either:
|
||||
/// <list type="bullet">
|
||||
/// <item><see cref="CreateNext"/> is called again</item>
|
||||
/// <item>The token passed to this method (if any) is cancelled</item>
|
||||
/// <item>The token passed to the constructor (if any) is cancelled</item>
|
||||
/// <item><see cref="Dispose"/> is called</item>
|
||||
/// </list>
|
||||
/// </returns>
|
||||
/// <exception cref="ObjectDisposedException">This object has been disposed.</exception>
|
||||
public CancellationToken CreateNext(CancellationToken token = default)
|
||||
{
|
||||
var nextSource = CancellationTokenSource.CreateLinkedTokenSource(token, _superToken);
|
||||
|
||||
// Obtain the token before exchange, as otherwise the CTS may be cancelled before
|
||||
// we request the Token, which will result in an ObjectDisposedException.
|
||||
// This way we would return a cancelled token, which is reasonable.
|
||||
var nextToken = nextSource.Token;
|
||||
|
||||
// The following block is identical to Interlocked.Exchange, except no replacement is made if the current
|
||||
// field value is null (latch on null). This ensures state is not corrupted if CreateNext is called after
|
||||
// the object is disposed.
|
||||
var priorSource = Volatile.Read(ref _cts);
|
||||
while (priorSource is not null)
|
||||
{
|
||||
var candidate = Interlocked.CompareExchange(ref _cts, nextSource, priorSource);
|
||||
|
||||
if (candidate == priorSource)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
priorSource = candidate;
|
||||
}
|
||||
}
|
||||
|
||||
if (priorSource == null)
|
||||
{
|
||||
nextSource.Dispose();
|
||||
|
||||
throw new ObjectDisposedException(nameof(CancellationSeries));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
priorSource.Cancel();
|
||||
}
|
||||
finally
|
||||
{
|
||||
// A registered action on the token may throw, which would surface here.
|
||||
// Ensure we always dispose the prior CTS.
|
||||
priorSource.Dispose();
|
||||
}
|
||||
|
||||
return nextToken;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
var source = Interlocked.Exchange(ref _cts, null);
|
||||
|
||||
if (source == null)
|
||||
{
|
||||
// Already disposed
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
source.Cancel();
|
||||
}
|
||||
finally
|
||||
{
|
||||
source.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -30,6 +30,10 @@
|
|||
<PackageReference Include="StreamJsonRpc" Version="$(StreamJsonRpcVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="Roslyn.SyntaxVisualizer.Extension" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Resource Include="SyntaxGraph.ico" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -6,6 +6,7 @@ using System.Collections.Immutable;
|
|||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
|
@ -332,20 +333,19 @@ namespace Roslyn.SyntaxVisualizer.Control
|
|||
// the children for any given item are only populated when the item is selected. If lazy is
|
||||
// false then the entire tree is populated at once (and this can result in bad performance when
|
||||
// displaying large trees).
|
||||
public void DisplaySyntaxTree(Document document, SyntaxTree tree, SemanticModel? model = null, bool lazy = true, Workspace? workspace = null)
|
||||
public async Task DisplaySyntaxTreeAsync(Document document, SyntaxTree tree, SemanticModel? model, bool lazy, Workspace? workspace, CancellationToken cancellationToken)
|
||||
{
|
||||
if (tree != null)
|
||||
{
|
||||
IsLazy = lazy;
|
||||
SyntaxTree = tree;
|
||||
SemanticModel = model;
|
||||
AddNode(null, SyntaxTree.GetRoot());
|
||||
AddNode(null, await SyntaxTree.GetRootAsync(cancellationToken));
|
||||
|
||||
if (model != null && workspace != null)
|
||||
{
|
||||
var root = ThreadHelper.JoinableTaskFactory.Run(() => tree.GetRootAsync());
|
||||
classifiedSpans = ThreadHelper.JoinableTaskFactory.Run(
|
||||
() => Classifier.GetClassifiedSpansAsync(document, root.FullSpan, CancellationToken.None)).ToImmutableArray();
|
||||
var root = await tree.GetRootAsync(cancellationToken);
|
||||
classifiedSpans = (await Classifier.GetClassifiedSpansAsync(document, root.FullSpan, cancellationToken)).ToImmutableArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -19,6 +19,7 @@ using Microsoft.VisualStudio.Text.Classification;
|
|||
using Microsoft.VisualStudio.Text.Editor;
|
||||
|
||||
using Roslyn.SyntaxVisualizer.DgmlHelper;
|
||||
using Roslyn.Utilities;
|
||||
|
||||
namespace Roslyn.SyntaxVisualizer.Extension
|
||||
{
|
||||
|
@ -27,6 +28,7 @@ namespace Roslyn.SyntaxVisualizer.Extension
|
|||
internal partial class SyntaxVisualizerContainer : UserControl, IVsRunningDocTableEvents, IVsSolutionEvents, IDisposable
|
||||
{
|
||||
private readonly SyntaxVisualizerToolWindow parent;
|
||||
private readonly CancellationSeries cancellationSeries = new();
|
||||
private IWpfTextView? activeWpfTextView;
|
||||
private IClassificationFormatMap? activeClassificationFormatMap;
|
||||
private IEditorFormatMap? activeEditorFormatMap;
|
||||
|
@ -242,6 +244,8 @@ namespace Roslyn.SyntaxVisualizer.Extension
|
|||
#pragma warning restore VSTHRD010 // Invoke single-threaded types on Main thread
|
||||
runningDocumentTableCookie = 0;
|
||||
}
|
||||
|
||||
cancellationSeries.Dispose();
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
@ -251,32 +255,44 @@ namespace Roslyn.SyntaxVisualizer.Extension
|
|||
// that is currently active in the editor.
|
||||
private void RefreshSyntaxVisualizer()
|
||||
{
|
||||
if (IsVisible && activeWpfTextView != null)
|
||||
var cancellationToken = cancellationSeries.CreateNext();
|
||||
|
||||
if (!IsVisible || activeWpfTextView == null)
|
||||
{
|
||||
var snapshot = activeWpfTextView.TextBuffer.CurrentSnapshot;
|
||||
var contentType = snapshot.ContentType;
|
||||
|
||||
if (contentType.IsOfType(VisualBasicContentType) ||
|
||||
contentType.IsOfType(CSharpContentType))
|
||||
{
|
||||
// Get the Document corresponding to the currently active text snapshot.
|
||||
var document = snapshot.GetOpenDocumentInCurrentContextWithChanges();
|
||||
if (document != null)
|
||||
{
|
||||
// Get the SyntaxTree and SemanticModel corresponding to the Document.
|
||||
activeSyntaxTree = ThreadHelper.JoinableTaskFactory.Run(() => document.GetSyntaxTreeAsync());
|
||||
var activeSemanticModel = ThreadHelper.JoinableTaskFactory.Run(() => document.GetSemanticModelAsync());
|
||||
|
||||
// Display the SyntaxTree.
|
||||
if ((contentType.IsOfType(VisualBasicContentType) || contentType.IsOfType(CSharpContentType)) && activeSyntaxTree is not null)
|
||||
{
|
||||
syntaxVisualizer.DisplaySyntaxTree(document, activeSyntaxTree, activeSemanticModel, workspace: document.Project.Solution.Workspace);
|
||||
}
|
||||
|
||||
NavigateFromSource();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var snapshot = activeWpfTextView.TextBuffer.CurrentSnapshot;
|
||||
var contentType = snapshot.ContentType;
|
||||
|
||||
if (!contentType.IsOfType(VisualBasicContentType)
|
||||
&& !contentType.IsOfType(CSharpContentType))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the Document corresponding to the currently active text snapshot.
|
||||
var document = snapshot.GetOpenDocumentInCurrentContextWithChanges();
|
||||
if (document == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_ = ThreadHelper.JoinableTaskFactory.RunAsync(
|
||||
async () =>
|
||||
{
|
||||
// Get the SyntaxTree and SemanticModel corresponding to the Document.
|
||||
activeSyntaxTree = await document.GetSyntaxTreeAsync(cancellationToken);
|
||||
var activeSemanticModel = await document.GetSemanticModelAsync(cancellationToken);
|
||||
|
||||
// Display the SyntaxTree.
|
||||
if (activeSyntaxTree is not null)
|
||||
{
|
||||
await syntaxVisualizer.DisplaySyntaxTreeAsync(document, activeSyntaxTree, activeSemanticModel, lazy: true, document.Project.Solution.Workspace, cancellationToken);
|
||||
}
|
||||
|
||||
NavigateFromSource();
|
||||
});
|
||||
}
|
||||
|
||||
// When user clicks / selects text in the editor select the corresponding item in the treeview.
|
||||
|
|
Загрузка…
Ссылка в новой задаче