提交 b17d1881 编写于 作者: C CyrusNajmabadi

Merge pull request #8050 from CyrusNajmabadi/searchProjectsInParallel

Search projects in parallel.  Cancel any parallel work once enough results are found.
......@@ -26,12 +26,12 @@ internal abstract partial class AbstractAddImportCodeFixProvider<TSimpleNameSynt
private abstract class SearchScope
{
public readonly bool Exact;
protected readonly CancellationToken cancellationToken;
public readonly CancellationToken CancellationToken;
protected SearchScope(bool exact, CancellationToken cancellationToken)
{
this.Exact = exact;
this.cancellationToken = cancellationToken;
Exact = exact;
CancellationToken = cancellationToken;
}
protected abstract Task<IEnumerable<ISymbol>> FindDeclarationsAsync(string name, SymbolFilter filter, SearchQuery query);
......@@ -102,7 +102,7 @@ public AllSymbolsProjectSearchScope(Project project, bool ignoreCase, Cancellati
protected override Task<IEnumerable<ISymbol>> FindDeclarationsAsync(string name, SymbolFilter filter, SearchQuery searchQuery)
{
return SymbolFinder.FindDeclarationsAsync(_project, searchQuery, filter, cancellationToken);
return SymbolFinder.FindDeclarationsAsync(_project, searchQuery, filter, CancellationToken);
}
}
......@@ -125,7 +125,7 @@ private class SourceSymbolsProjectSearchScope : ProjectSearchScope
protected override async Task<IEnumerable<ISymbol>> FindDeclarationsAsync(string name, SymbolFilter filter, SearchQuery searchQuery)
{
var service = _project.Solution.Workspace.Services.GetService<ISymbolTreeInfoCacheService>();
var info = await service.TryGetSymbolTreeInfoAsync(_project, cancellationToken).ConfigureAwait(false);
var info = await service.TryGetSymbolTreeInfoAsync(_project, CancellationToken).ConfigureAwait(false);
if (info == null)
{
// Looks like there was nothing in the cache. Return no results for now.
......@@ -137,7 +137,7 @@ protected override async Task<IEnumerable<ISymbol>> FindDeclarationsAsync(string
// needed.
var lazyAssembly = _projectToAssembly.GetOrAdd(_project, CreateLazyAssembly);
return await info.FindAsync(searchQuery, lazyAssembly, cancellationToken).ConfigureAwait(false);
return await info.FindAsync(searchQuery, lazyAssembly, CancellationToken).ConfigureAwait(false);
}
private static AsyncLazy<IAssemblySymbol> CreateLazyAssembly(Project project)
......@@ -180,13 +180,13 @@ public override SymbolReference CreateReference<T>(SearchResult<T> searchResult)
protected override async Task<IEnumerable<ISymbol>> FindDeclarationsAsync(string name, SymbolFilter filter, SearchQuery searchQuery)
{
var service = _solution.Workspace.Services.GetService<ISymbolTreeInfoCacheService>();
var info = await service.TryGetSymbolTreeInfoAsync(_solution, _assembly, _metadataReference, cancellationToken).ConfigureAwait(false);
var info = await service.TryGetSymbolTreeInfoAsync(_solution, _assembly, _metadataReference, CancellationToken).ConfigureAwait(false);
if (info == null)
{
return SpecializedCollections.EmptyEnumerable<ISymbol>();
}
return await info.FindAsync(searchQuery, _assembly, cancellationToken).ConfigureAwait(false);
return await info.FindAsync(searchQuery, _assembly, CancellationToken).ConfigureAwait(false);
}
}
}
......
......@@ -15,7 +15,6 @@ internal abstract partial class AbstractAddImportCodeFixProvider<TSimpleNameSynt
{
private class SymbolReferenceFinder
{
private readonly CancellationToken _cancellationToken;
private readonly Diagnostic _diagnostic;
private readonly Document _document;
private readonly SemanticModel _semanticModel;
......@@ -37,7 +36,6 @@ private class SymbolReferenceFinder
_semanticModel = semanticModel;
_diagnostic = diagnostic;
_node = node;
_cancellationToken = cancellationToken;
_containingType = semanticModel.GetEnclosingNamedType(node.SpanStart, cancellationToken);
_containingTypeOrAssembly = _containingType ?? (ISymbol)semanticModel.Compilation.Assembly;
......@@ -46,29 +44,31 @@ private class SymbolReferenceFinder
}
internal Task<List<SymbolReference>> FindInAllProjectSymbolsAsync(
Project project, bool exact)
Project project, bool exact, CancellationToken cancellationToken)
{
var searchScope = new AllSymbolsProjectSearchScope(project, exact, _cancellationToken);
var searchScope = new AllSymbolsProjectSearchScope(project, exact, cancellationToken);
return DoAsync(searchScope);
}
internal Task<List<SymbolReference>> FindInSourceProjectSymbolsAsync(
ConcurrentDictionary<Project, AsyncLazy<IAssemblySymbol>> projectToAssembly,
Project project, bool exact)
Project project, bool exact, CancellationToken cancellationToken)
{
var searchScope = new SourceSymbolsProjectSearchScope(projectToAssembly, project, exact, _cancellationToken);
var searchScope = new SourceSymbolsProjectSearchScope(projectToAssembly, project, exact, cancellationToken);
return DoAsync(searchScope);
}
internal Task<List<SymbolReference>> FindInMetadataAsync(
Solution solution, IAssemblySymbol assembly, PortableExecutableReference metadataReference, bool exact)
Solution solution, IAssemblySymbol assembly, PortableExecutableReference metadataReference, bool exact, CancellationToken cancellationToken)
{
var searchScope = new MetadataSymbolsSearchScope(solution, assembly, metadataReference, exact, _cancellationToken);
var searchScope = new MetadataSymbolsSearchScope(solution, assembly, metadataReference, exact, cancellationToken);
return DoAsync(searchScope);
}
private async Task<List<SymbolReference>> DoAsync(SearchScope searchScope)
{
searchScope.CancellationToken.ThrowIfCancellationRequested();
// Spin off tasks to do all our searching.
var tasks = new List<Task<IList<SymbolReference>>>
{
......@@ -92,7 +92,7 @@ private async Task<List<SymbolReference>> DoAsync(SearchScope searchScope)
}
await Task.WhenAll(tasks).ConfigureAwait(false);
_cancellationToken.ThrowIfCancellationRequested();
searchScope.CancellationToken.ThrowIfCancellationRequested();
List<SymbolReference> allReferences = null;
foreach (var task in tasks)
......@@ -125,6 +125,8 @@ private List<SymbolReference> DeDupeAndSortReferences(List<SymbolReference> allR
private async Task<IList<SymbolReference>> GetNamespacesForMatchingTypesAsync(SearchScope searchScope)
{
searchScope.CancellationToken.ThrowIfCancellationRequested();
TSimpleNameSyntax nameNode;
if (!_owner.CanAddImportForType(_diagnostic, _node, out nameNode))
{
......@@ -147,6 +149,8 @@ private async Task<IList<SymbolReference>> GetNamespacesForMatchingTypesAsync(Se
private async Task<IList<SymbolReference>> GetMatchingTypesAsync(SearchScope searchScope)
{
searchScope.CancellationToken.ThrowIfCancellationRequested();
TSimpleNameSyntax nameNode;
if (!_owner.CanAddImportForType(_diagnostic, _node, out nameNode))
{
......@@ -173,12 +177,12 @@ private async Task<IList<SymbolReference>> GetMatchingTypesAsync(SearchScope sea
TSimpleNameSyntax nameNode,
bool inAttributeContext)
{
if (_cancellationToken.IsCancellationRequested)
if (searchScope.CancellationToken.IsCancellationRequested)
{
return null;
}
if (ExpressionBinds(nameNode, checkForExtensionMethods: false))
if (ExpressionBinds(searchScope, nameNode, checkForExtensionMethods: false))
{
return null;
}
......@@ -197,12 +201,12 @@ private async Task<IList<SymbolReference>> GetMatchingTypesAsync(SearchScope sea
return OfType<ITypeSymbol>(symbols);
}
protected bool ExpressionBinds(TSimpleNameSyntax nameNode, bool checkForExtensionMethods)
protected bool ExpressionBinds(SearchScope searchScope, TSimpleNameSyntax nameNode, bool checkForExtensionMethods)
{
// See if the name binds to something other then the error type. If it does, there's nothing further we need to do.
// For extension methods, however, we will continue to search if there exists any better matched method.
_cancellationToken.ThrowIfCancellationRequested();
var symbolInfo = _semanticModel.GetSymbolInfo(nameNode, _cancellationToken);
searchScope.CancellationToken.ThrowIfCancellationRequested();
var symbolInfo = _semanticModel.GetSymbolInfo(nameNode, searchScope.CancellationToken);
if (symbolInfo.CandidateReason == CandidateReason.OverloadResolutionFailure && !checkForExtensionMethods)
{
return true;
......@@ -214,6 +218,8 @@ protected bool ExpressionBinds(TSimpleNameSyntax nameNode, bool checkForExtensio
private async Task<IList<SymbolReference>> GetNamespacesForMatchingNamespacesAsync(
SearchScope searchScope)
{
searchScope.CancellationToken.ThrowIfCancellationRequested();
TSimpleNameSyntax nameNode;
if (!_owner.CanAddImportForNamespace(_diagnostic, _node, out nameNode))
{
......@@ -229,7 +235,7 @@ protected bool ExpressionBinds(TSimpleNameSyntax nameNode, bool checkForExtensio
return null;
}
if (ExpressionBinds(nameNode, checkForExtensionMethods: false))
if (ExpressionBinds(searchScope, nameNode, checkForExtensionMethods: false))
{
return null;
}
......@@ -242,6 +248,8 @@ protected bool ExpressionBinds(TSimpleNameSyntax nameNode, bool checkForExtensio
private async Task<IList<SymbolReference>> GetNamespacesForMatchingExtensionMethodsAsync(SearchScope searchScope)
{
searchScope.CancellationToken.ThrowIfCancellationRequested();
TSimpleNameSyntax nameNode;
if (!_owner.CanAddImportForMethod(_diagnostic, _syntaxFacts, _node, out nameNode))
{
......@@ -261,6 +269,8 @@ private async Task<IList<SymbolReference>> GetNamespacesForMatchingExtensionMeth
private async Task<IList<SymbolReference>> GetNamespacesForCollectionInitializerMethodsAsync(SearchScope searchScope)
{
searchScope.CancellationToken.ThrowIfCancellationRequested();
TSimpleNameSyntax nameNode;
if (!_owner.CanAddImportForMethod(_diagnostic, _syntaxFacts, _node, out nameNode))
{
......@@ -274,6 +284,8 @@ private async Task<IList<SymbolReference>> GetNamespacesForCollectionInitializer
private async Task<IList<SymbolReference>> GetNamespacesForMatchingFieldsAndPropertiesAsync(
SearchScope searchScope)
{
searchScope.CancellationToken.ThrowIfCancellationRequested();
TSimpleNameSyntax nameNode;
if (!_owner.CanAddImportForMethod(_diagnostic, _syntaxFacts, _node, out nameNode))
{
......@@ -296,12 +308,14 @@ private async Task<IList<SymbolReference>> GetNamespacesForCollectionInitializer
private async Task<IList<SymbolReference>> GetNamespacesForQueryPatternsAsync(SearchScope searchScope)
{
searchScope.CancellationToken.ThrowIfCancellationRequested();
if (!_owner.CanAddImportForQuery(_diagnostic, _node))
{
return null;
}
ITypeSymbol type = _owner.GetQueryClauseInfo(_semanticModel, _node, _cancellationToken);
ITypeSymbol type = _owner.GetQueryClauseInfo(_semanticModel, _node, searchScope.CancellationToken);
if (type == null)
{
return null;
......@@ -380,10 +394,10 @@ private List<SymbolReference> GetProposedTypes(SearchScope searchScope, string n
private Task<IEnumerable<SearchResult<ISymbol>>> GetSymbolsAsync(SearchScope searchScope, TSimpleNameSyntax nameNode)
{
_cancellationToken.ThrowIfCancellationRequested();
searchScope.CancellationToken.ThrowIfCancellationRequested();
// See if the name binds. If it does, there's nothing further we need to do.
if (ExpressionBinds(nameNode, checkForExtensionMethods: true))
if (ExpressionBinds(searchScope, nameNode, checkForExtensionMethods: true))
{
return SpecializedTasks.EmptyEnumerable<SearchResult<ISymbol>>();
}
......@@ -405,7 +419,7 @@ private Task<IEnumerable<SearchResult<ISymbol>>> GetSymbolsAsync(SearchScope sea
var extensionMethodSymbols = OfType<IMethodSymbol>(symbols)
.Where(s => s.Symbol.IsExtensionMethod &&
s.Symbol.IsAccessibleWithin(_semanticModel.Compilation.Assembly) == true &&
_owner.IsViableExtensionMethod(s.Symbol, expression, _semanticModel, _syntaxFacts, _cancellationToken))
_owner.IsViableExtensionMethod(s.Symbol, expression, _semanticModel, _syntaxFacts, searchScope.CancellationToken))
.ToList();
return GetProposedNamespaces(
......@@ -417,12 +431,12 @@ private Task<IEnumerable<SearchResult<ISymbol>>> GetSymbolsAsync(SearchScope sea
{
var propertySymbols = OfType<IPropertySymbol>(symbols)
.Where(property => property.Symbol.IsAccessibleWithin(_semanticModel.Compilation.Assembly) == true &&
_owner.IsViableProperty(property.Symbol, expression, _semanticModel, _syntaxFacts, _cancellationToken))
_owner.IsViableProperty(property.Symbol, expression, _semanticModel, _syntaxFacts, searchScope.CancellationToken))
.ToList();
var fieldSymbols = OfType<IFieldSymbol>(symbols)
.Where(field => field.Symbol.IsAccessibleWithin(_semanticModel.Compilation.Assembly) == true &&
_owner.IsViableField(field.Symbol, expression, _semanticModel, _syntaxFacts, _cancellationToken))
_owner.IsViableField(field.Symbol, expression, _semanticModel, _syntaxFacts, searchScope.CancellationToken))
.ToList();
return GetProposedNamespaces(
......@@ -449,7 +463,7 @@ private Task<IEnumerable<SearchResult<ISymbol>>> GetSymbolsAsync(SearchScope sea
return OfType<IMethodSymbol>(symbols)
.Where(s => s.Symbol.IsExtensionMethod &&
s.Symbol.IsAccessibleWithin(_semanticModel.Compilation.Assembly) == true &&
_owner.IsViableExtensionMethod(s.Symbol, expression, _semanticModel, _syntaxFacts, _cancellationToken))
_owner.IsViableExtensionMethod(s.Symbol, expression, _semanticModel, _syntaxFacts, searchScope.CancellationToken))
.Select(s => s.WithDesiredName(null));
}
......
......@@ -118,7 +118,7 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
// First search the current project to see if any symbols (source or metadata) match the
// search string.
await FindResultsInAllProjectSymbolsAsync(project, allSymbolReferences, finder, exact).ConfigureAwait(false);
await FindResultsInAllProjectSymbolsAsync(project, allSymbolReferences, finder, exact, cancellationToken).ConfigureAwait(false);
// Now search unreferenced projects, and see if they have any source symbols that match
// the search string.
......@@ -129,9 +129,9 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
}
private async Task FindResultsInAllProjectSymbolsAsync(
Project project, List<SymbolReference> allSymbolReferences, SymbolReferenceFinder finder, bool exact)
Project project, List<SymbolReference> allSymbolReferences, SymbolReferenceFinder finder, bool exact, CancellationToken cancellationToken)
{
var references = await finder.FindInAllProjectSymbolsAsync(project, exact).ConfigureAwait(false);
var references = await finder.FindInAllProjectSymbolsAsync(project, exact, cancellationToken).ConfigureAwait(false);
AddRange(allSymbolReferences, references);
}
......@@ -187,17 +187,25 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
}
var viableUnreferencedProjects = GetViableUnreferencedProjects(project);
foreach (var unreferencedProject in viableUnreferencedProjects)
// Search all unreferenced projects in parallel.
var findTasks = new HashSet<Task<List<SymbolReference>>>();
// Create another cancellation token so we can both search all projects in parallel,
// but also stop any searches once we get enough results.
using (var nestedTokenSource = new CancellationTokenSource())
using (var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(nestedTokenSource.Token, cancellationToken))
{
// Search in this unreferenced project. But don't search in any of its'
// direct references. i.e. we don't want to search in its metadata references
// or in the projects it references itself. We'll be searching those entities
// individually.
AddRange(allSymbolReferences, await finder.FindInSourceProjectSymbolsAsync(projectToAssembly, unreferencedProject, exact: exact).ConfigureAwait(false));
if (allSymbolReferences.Count >= MaxResults)
foreach (var unreferencedProject in viableUnreferencedProjects)
{
return;
// Search in this unreferenced project. But don't search in any of its'
// direct references. i.e. we don't want to search in its metadata references
// or in the projects it references itself. We'll be searching those entities
// individually.
findTasks.Add(finder.FindInSourceProjectSymbolsAsync(projectToAssembly, unreferencedProject, exact, linkedTokenSource.Token));
}
await WaitForTasksAsync(allSymbolReferences, findTasks, nestedTokenSource, cancellationToken).ConfigureAwait(false);
}
}
......@@ -230,40 +238,60 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
// Search all metadata references in parallel.
var findTasks = new HashSet<Task<List<SymbolReference>>>();
foreach (var reference in newReferences)
// Create another cancellation token so we can both search all projects in parallel,
// but also stop any searches once we get enough results.
using (var nestedTokenSource = new CancellationTokenSource())
using (var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(nestedTokenSource.Token, cancellationToken))
{
var compilation = referenceToCompilation.GetOrAdd(reference, r => CreateCompilation(project, r));
// Ignore netmodules. First, they're incredibly esoteric and barely used.
// Second, the SymbolFinder api doesn't even support searching them.
var assembly = compilation.GetAssemblyOrModuleSymbol(reference) as IAssemblySymbol;
if (assembly != null)
foreach (var reference in newReferences)
{
findTasks.Add(finder.FindInMetadataAsync(project.Solution, assembly, reference, exact));
var compilation = referenceToCompilation.GetOrAdd(reference, r => CreateCompilation(project, r));
// Ignore netmodules. First, they're incredibly esoteric and barely used.
// Second, the SymbolFinder api doesn't even support searching them.
var assembly = compilation.GetAssemblyOrModuleSymbol(reference) as IAssemblySymbol;
if (assembly != null)
{
findTasks.Add(finder.FindInMetadataAsync(project.Solution, assembly, reference, exact, linkedTokenSource.Token));
}
}
await WaitForTasksAsync(allSymbolReferences, findTasks, nestedTokenSource, cancellationToken).ConfigureAwait(false);
}
}
while (findTasks.Count > 0)
private async Task WaitForTasksAsync(
List<SymbolReference> allSymbolReferences,
HashSet<Task<List<SymbolReference>>> findTasks,
CancellationTokenSource nestedTokenSource,
CancellationToken cancellationToken)
{
try
{
// Keep on looping through the 'find' tasks, processing each when they finish.
cancellationToken.ThrowIfCancellationRequested();
var doneTask = await Task.WhenAny(findTasks).ConfigureAwait(false);
while (findTasks.Count > 0)
{
// Keep on looping through the 'find' tasks, processing each when they finish.
cancellationToken.ThrowIfCancellationRequested();
var doneTask = await Task.WhenAny(findTasks).ConfigureAwait(false);
// One of the tasks finished. Remove it from the list we're waiting on.
findTasks.Remove(doneTask);
// One of the tasks finished. Remove it from the list we're waiting on.
findTasks.Remove(doneTask);
// Add its results to the final result set we're keeping.
AddRange(allSymbolReferences, await doneTask.ConfigureAwait(false));
// Add its results to the final result set we're keeping.
AddRange(allSymbolReferences, await doneTask.ConfigureAwait(false));
// If we've got enough, no need to keep searching.
// Note: We do not cancel the existing tasks that are still executing. These tasks will
// cause our indices to be created if necessary. And that's good for future searches which
// we will invariably perform.
if (allSymbolReferences.Count >= MaxResults)
{
break;
// Once we get enough, just stop.
if (allSymbolReferences.Count >= MaxResults)
{
return;
}
}
}
finally
{
// Cancel any nested work that's still happening.
nestedTokenSource.Cancel();
}
}
/// <summary>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册