private Task BuildTreeForSnapshotAsync(IDependenciesSnapshot snapshot) { var viewProvider = ViewProviders.FirstOrDefault(); if (viewProvider == null) { return(Task.CompletedTask); } var nowait = SubmitTreeUpdateAsync( (treeSnapshot, configuredProjectExports, cancellationToken) => { var dependenciesNode = treeSnapshot.Value.Tree; if (!cancellationToken.IsCancellationRequested) { dependenciesNode = viewProvider.Value.BuildTree(dependenciesNode, snapshot, cancellationToken); } // TODO We still are getting mismatched data sources and need to figure out better // way of merging, mute them for now and get to it in U1 return(Task.FromResult(new TreeUpdateResult(dependenciesNode, false, null))); }); return(Task.CompletedTask); }
private ITargetedDependenciesSnapshot GetSnapshot(string projectPath, IDependency dependency, out string dependencyProjectPath) { dependencyProjectPath = dependency.FullPath; IDependenciesSnapshotProvider snapshotProvider = AggregateSnapshotProvider.GetSnapshotProvider(dependencyProjectPath); if (snapshotProvider == null) { return(null); } IDependenciesSnapshot snapshot = snapshotProvider.CurrentSnapshot; if (snapshot == null) { return(null); } ITargetFramework targetFramework = TargetFrameworkProvider.GetNearestFramework( dependency.TargetFramework, snapshot.Targets.Keys); if (targetFramework == null) { return(null); } return(snapshot.Targets[targetFramework]); }
public SnapshotChangedEventArgs(IDependenciesSnapshot snapshot, CancellationToken token) { Requires.NotNull(snapshot, nameof(snapshot)); Snapshot = snapshot; Token = token; }
protected IDependency GetDependency( IGraphContext graphContext, GraphNode inputGraphNode, out IDependenciesSnapshot snapshot) { snapshot = null; var projectPath = inputGraphNode.Id.GetValue(CodeGraphNodeIdName.Assembly); if (string.IsNullOrEmpty(projectPath)) { return(null); } var projectFolder = Path.GetDirectoryName(projectPath); var id = inputGraphNode.GetValue <string>(DependenciesGraphSchema.DependencyIdProperty); if (id == null) { // this is top level node and it contains full path id = inputGraphNode.Id.GetValue(CodeGraphNodeIdName.File); if (id.StartsWith(projectFolder, StringComparison.OrdinalIgnoreCase)) { id = id.Substring(projectFolder.Length).TrimStart('\\'); } return(GetTopLevelDependency(projectPath, id, out snapshot)); } else { return(GetDependency(projectPath, id, out snapshot)); } }
private Task BuildTreeForSnapshotAsync(IDependenciesSnapshot snapshot) { Lazy <IDependenciesTreeViewProvider, IOrderPrecedenceMetadataView> viewProvider = ViewProviders.FirstOrDefault(); if (viewProvider == null) { return(Task.CompletedTask); } Task <IProjectVersionedValue <IProjectTreeSnapshot> > nowait = SubmitTreeUpdateAsync( async(treeSnapshot, configuredProjectExports, cancellationToken) => { IProjectTree dependenciesNode = treeSnapshot.Value.Tree; if (!cancellationToken.IsCancellationRequested) { dependenciesNode = await viewProvider.Value.BuildTreeAsync(dependenciesNode, snapshot, cancellationToken) .ConfigureAwait(false); _treeTelemetryService.ObserveTreeUpdateCompleted(snapshot.HasUnresolvedDependency); } // TODO We still are getting mismatched data sources and need to figure out better // way of merging, mute them for now and get to it in U1 return(new TreeUpdateResult(dependenciesNode, false, null)); }); return(Task.CompletedTask); }
/// <summary> /// Executes <paramref name="updateFunc"/> on the current snapshot within a lock. /// If a different snapshot object is returned, <see cref="CurrentSnapshot"/> is updated /// and an invocation of <see cref="SnapshotChanged"/> is scheduled. /// </summary> private void TryUpdateSnapshot(Func <DependenciesSnapshot, DependenciesSnapshot> updateFunc, CancellationToken token = default) { lock (_snapshotLock) { DependenciesSnapshot updatedSnapshot = updateFunc(_currentSnapshot); if (ReferenceEquals(_currentSnapshot, updatedSnapshot)) { return; } _currentSnapshot = updatedSnapshot; } // avoid unnecessary tree updates _dependenciesUpdateScheduler.ScheduleAsyncTask( ct => { if (ct.IsCancellationRequested || IsDisposing || IsDisposed) { return(Task.FromCanceled(ct)); } IDependenciesSnapshot snapshot = _currentSnapshot; if (snapshot != null) { SnapshotChanged?.Invoke(this, new SnapshotChangedEventArgs(snapshot, ct)); } return(Task.CompletedTask); }, token); }
private void TryUpdateSnapshot(Func <DependenciesSnapshot, DependenciesSnapshot> updateFunc, CancellationToken token = default) { lock (_snapshotLock) { DependenciesSnapshot updatedSnapshot = updateFunc(_currentSnapshot); if (ReferenceEquals(_currentSnapshot, updatedSnapshot)) { return; } _currentSnapshot = updatedSnapshot; } // Conflate rapid snapshot updates by debouncing events over a short window. // This reduces the frequency of tree updates with minimal perceived latency. _dependenciesUpdateScheduler.ScheduleAsyncTask( ct => { if (ct.IsCancellationRequested || IsDisposing || IsDisposed) { return(Task.FromCanceled(ct)); } // Always publish the latest snapshot IDependenciesSnapshot snapshot = _currentSnapshot; var events = new SnapshotChangedEventArgs(snapshot, ct); _snapshotChangedSource.Post(events); SnapshotChanged?.Invoke(this, events); return(Task.CompletedTask); }, token); }
private void OnDependenciesSnapshotChanged(object sender, SnapshotChangedEventArgs e) { IDependenciesSnapshot snapshot = e.Snapshot; if (snapshot == null) { return; } lock (_treeUpdateLock) { if (_treeUpdateQueueTask == null || _treeUpdateQueueTask.IsCompleted) { _treeUpdateQueueTask = ThreadingService.JoinableTaskFactory.RunAsync(async() => { if (TasksService.UnloadCancellationToken.IsCancellationRequested) { return; } await BuildTreeForSnapshotAsync(snapshot).ConfigureAwait(false); }).Task; } else { _treeUpdateQueueTask = _treeUpdateQueueTask.ContinueWith( t => BuildTreeForSnapshotAsync(snapshot), TaskScheduler.Default); } } }
protected IDependency GetTopLevelDependency( string projectPath, string dependencyId, out IDependenciesSnapshot snapshot) { snapshot = GetSnapshot(projectPath); return(snapshot?.FindDependency(dependencyId, topLevel: true)); }
protected abstract void ProcessInputNode( IGraphContext graphContext, GraphNode inputGraphNode, IDependency dependency, IDependenciesSnapshot snapshot, IDependenciesGraphViewProvider viewProvider, string projectPath, ref bool trackChanges);
private IDependency FindDependency(GraphNode inputGraphNode, out IDependenciesSnapshot snapshot) { string projectPath = inputGraphNode.Id.GetValue(CodeGraphNodeIdName.Assembly); if (string.IsNullOrWhiteSpace(projectPath)) { snapshot = null; return(null); } string projectFolder = Path.GetDirectoryName(projectPath); if (projectFolder == null) { snapshot = null; return(null); } string id = inputGraphNode.GetValue <string>(DependenciesGraphSchema.DependencyIdProperty); bool topLevel; if (id == null) { // this is top level node and it contains full path id = inputGraphNode.Id.GetValue(CodeGraphNodeIdName.File); if (id == null) { // No full path, so this must be a node generated by a different provider. snapshot = null; return(null); } if (id.StartsWith(projectFolder, StringComparison.OrdinalIgnoreCase)) { int startIndex = projectFolder.Length; // Trim backslashes (without allocating) while (startIndex < id.Length && id[startIndex] == '\\') { startIndex++; } id = id.Substring(startIndex); } topLevel = true; } else { topLevel = false; } snapshot = AggregateSnapshotProvider.GetSnapshot(projectPath); return(snapshot?.FindDependency(id, topLevel)); }
public bool Equals(IDependenciesSnapshot other) { if (other != null && other.ProjectPath.Equals(ProjectPath, StringComparison.OrdinalIgnoreCase)) { return(true); } return(false); }
public override bool HandleChanges(IGraphContext graphContext, SnapshotChangedEventArgs e) { IDependenciesSnapshot snapshot = e.Snapshot; if (snapshot == null || e.Token.IsCancellationRequested) { return(false); } foreach (GraphNode inputGraphNode in graphContext.InputNodes.ToList()) { string existingDependencyId = inputGraphNode.GetValue <string>(DependenciesGraphSchema.DependencyIdProperty); if (string.IsNullOrEmpty(existingDependencyId)) { continue; } string projectPath = inputGraphNode.Id.GetValue(CodeGraphNodeIdName.Assembly); if (string.IsNullOrEmpty(projectPath)) { continue; } IDependency updatedDependency = GetDependency(projectPath, existingDependencyId, out IDependenciesSnapshot updatedSnapshot); if (updatedDependency == null) { continue; } IDependenciesGraphViewProvider viewProvider = ViewProviders .FirstOrDefaultValue((x, d) => x.SupportsDependency(d), updatedDependency); if (viewProvider == null) { continue; } if (!viewProvider.ShouldTrackChanges(projectPath, snapshot.ProjectPath, updatedDependency)) { continue; } using (var scope = new GraphTransactionScope()) { viewProvider.TrackChanges( graphContext, projectPath, updatedDependency, inputGraphNode, updatedSnapshot.Targets[updatedDependency.TargetFramework]); scope.Complete(); } } return(false); }
public static IEnumerable <IDependency> GetFlatTopLevelDependencies(this IDependenciesSnapshot self) { foreach ((ITargetFramework _, ITargetedDependenciesSnapshot targetedSnapshot) in self.DependenciesByTargetFramework) { foreach (IDependency dependency in targetedSnapshot.TopLevelDependencies) { yield return(dependency); } } }
public static IEnumerable <IDependency> GetFlatTopLevelDependencies(this IDependenciesSnapshot self) { foreach (ITargetedDependenciesSnapshot targetedSnapshot in self.Targets.Values) { foreach (IDependency dependency in targetedSnapshot.TopLevelDependencies) { yield return(dependency); } } }
private void ProcessSharedProjectsUpdates( IProjectSharedFoldersSnapshot sharedFolders, ITargetedProjectContext targetContext, DependenciesRuleChangeContext dependencyChangeContext) { Requires.NotNull(sharedFolders, nameof(sharedFolders)); Requires.NotNull(targetContext, nameof(targetContext)); Requires.NotNull(dependencyChangeContext, nameof(dependencyChangeContext)); IDependenciesSnapshot snapshot = _dependenciesSnapshotProvider.CurrentSnapshot; if (!snapshot.Targets.TryGetValue(targetContext.TargetFramework, out ITargetedDependenciesSnapshot targetedSnapshot)) { return; } IEnumerable <string> sharedFolderProjectPaths = sharedFolders.Value.Select(sf => sf.ProjectPath); var currentSharedImportNodes = targetedSnapshot.TopLevelDependencies .Where(x => x.Flags.Contains(DependencyTreeFlags.SharedProjectFlags)) .ToList(); IEnumerable <string> currentSharedImportNodePaths = currentSharedImportNodes.Select(x => x.Path); // process added nodes IEnumerable <string> addedSharedImportPaths = sharedFolderProjectPaths.Except(currentSharedImportNodePaths); foreach (string addedSharedImportPath in addedSharedImportPaths) { IDependencyModel added = new SharedProjectDependencyModel( addedSharedImportPath, addedSharedImportPath, isResolved: true, isImplicit: false, properties: ImmutableStringDictionary <string> .EmptyOrdinal); dependencyChangeContext.IncludeAddedChange(targetContext.TargetFramework, added); } // process removed nodes IEnumerable <string> removedSharedImportPaths = currentSharedImportNodePaths.Except(sharedFolderProjectPaths); foreach (string removedSharedImportPath in removedSharedImportPaths) { bool exists = currentSharedImportNodes.Any(node => PathHelper.IsSamePath(node.Path, removedSharedImportPath)); if (exists) { dependencyChangeContext.IncludeRemovedChange( targetContext.TargetFramework, ProjectRuleHandler.ProviderTypeString, dependencyId: removedSharedImportPath); } } }
private void OnDependenciesSnapshotChanged(object sender, SnapshotChangedEventArgs e) { IDependenciesSnapshot snapshot = e.Snapshot; if (snapshot == null) { return; } if (_tasksService.UnloadCancellationToken.IsCancellationRequested || e.Token.IsCancellationRequested) { return; } // Take the highest priority view provider IDependenciesTreeViewProvider viewProvider = _viewProviders.FirstOrDefault()?.Value; if (viewProvider == null) { return; } try { _ = SubmitTreeUpdateAsync( async(treeSnapshot, configuredProjectExports, cancellationToken) => { IProjectTree dependenciesNode = treeSnapshot.Value.Tree; if (!cancellationToken.IsCancellationRequested) { dependenciesNode = await viewProvider.BuildTreeAsync(dependenciesNode, snapshot, cancellationToken); await _treeTelemetryService.ObserveTreeUpdateCompletedAsync(snapshot.HasUnresolvedDependency); } // TODO We still are getting mismatched data sources and need to figure out better // way of merging, mute them for now and get to it in U1 return(new TreeUpdateResult(dependenciesNode)); }, _treeUpdateCancellationSeries.CreateNext(e.Token)); } catch (OperationCanceledException) { } catch (Exception ex) { // We do not expect any exception when we call SubmitTreeUpdateAsync, but we don't want to leak an exception here. // Because it will fail the dataflow block and stops updating the project tree silently. _ = ProjectFaultHandlerService.ReportFaultAsync(ex, UnconfiguredProject); } }
public static IDependenciesSnapshotProvider Implement( IDependenciesSnapshot currentSnapshot = null, MockBehavior mockBehavior = MockBehavior.Default) { var mock = new Mock <IDependenciesSnapshotProvider>(mockBehavior); if (currentSnapshot != null) { mock.Setup(x => x.CurrentSnapshot).Returns(currentSnapshot); } return(mock.Object); }
private void ProcessSharedProjectsUpdates( IProjectSharedFoldersSnapshot sharedFolders, ITargetFramework targetFramework, CrossTargetDependenciesChangesBuilder changesBuilder) { Requires.NotNull(sharedFolders, nameof(sharedFolders)); Requires.NotNull(targetFramework, nameof(targetFramework)); Requires.NotNull(changesBuilder, nameof(changesBuilder)); IDependenciesSnapshot snapshot = _dependenciesSnapshotProvider.CurrentSnapshot; if (!snapshot.Targets.TryGetValue(targetFramework, out ITargetedDependenciesSnapshot targetedSnapshot)) { return; } IEnumerable <string> sharedFolderProjectPaths = sharedFolders.Value.Select(sf => sf.ProjectPath); var currentSharedImportNodePaths = targetedSnapshot.TopLevelDependencies .Where(x => x.Flags.Contains(DependencyTreeFlags.SharedProjectFlags)) .Select(x => x.Path) .ToList(); var diff = new SetDiff <string>(currentSharedImportNodePaths, sharedFolderProjectPaths); // process added nodes foreach (string addedSharedImportPath in diff.Added) { IDependencyModel added = new SharedProjectDependencyModel( addedSharedImportPath, addedSharedImportPath, isResolved: true, isImplicit: false, properties: ImmutableStringDictionary <string> .EmptyOrdinal); changesBuilder.Added(targetFramework, added); } // process removed nodes foreach (string removedSharedImportPath in diff.Removed) { bool exists = currentSharedImportNodePaths.Any(nodePath => PathHelper.IsSamePath(nodePath, removedSharedImportPath)); if (exists) { changesBuilder.Removed( targetFramework, ProjectRuleHandler.ProviderTypeString, dependencyId: removedSharedImportPath); } } }
private void ProcessSharedProjectsUpdates( IProjectSharedFoldersSnapshot sharedFolders, ITargetedProjectContext targetContext, DependenciesRuleChangeContext dependencyChangeContext) { Requires.NotNull(sharedFolders, nameof(sharedFolders)); Requires.NotNull(targetContext, nameof(targetContext)); Requires.NotNull(dependencyChangeContext, nameof(dependencyChangeContext)); IDependenciesSnapshot snapshot = _dependenciesSnapshotProvider.CurrentSnapshot; if (!snapshot.Targets.TryGetValue(targetContext.TargetFramework, out ITargetedDependenciesSnapshot targetedSnapshot)) { return; } IEnumerable <string> sharedFolderProjectPaths = sharedFolders.Value.Select(sf => sf.ProjectPath); var currentSharedImportNodes = targetedSnapshot.TopLevelDependencies .Where(x => x.Flags.Contains(DependencyTreeFlags.SharedProjectFlags)) .ToList(); IEnumerable <string> currentSharedImportNodePaths = currentSharedImportNodes.Select(x => x.Path); // process added nodes IEnumerable <string> addedSharedImportPaths = sharedFolderProjectPaths.Except(currentSharedImportNodePaths); foreach (string addedSharedImportPath in addedSharedImportPaths) { IDependencyModel added = CreateDependencyModel(addedSharedImportPath, targetContext.TargetFramework, resolved: true); dependencyChangeContext.IncludeAddedChange(targetContext.TargetFramework, added); } // process removed nodes IEnumerable <string> removedSharedImportPaths = currentSharedImportNodePaths.Except(sharedFolderProjectPaths); foreach (string removedSharedImportPath in removedSharedImportPaths) { IDependency existingImportNode = currentSharedImportNodes .Where(node => PathHelper.IsSamePath(node.Path, removedSharedImportPath)) .FirstOrDefault(); if (existingImportNode != null) { IDependencyModel removed = CreateDependencyModel(removedSharedImportPath, targetContext.TargetFramework, resolved: true); dependencyChangeContext.IncludeRemovedChange(targetContext.TargetFramework, removed); } } }
/// <summary> /// Does flat search among dependency world lists to find any dependencies that match search criteria. /// </summary> private static HashSet <IDependency> SearchFlat(string searchTerm, IDependenciesSnapshot dependenciesSnapshot) { var matchedDependencies = new HashSet <IDependency>(); foreach ((ITargetFramework _, ITargetedDependenciesSnapshot targetedSnapshot) in dependenciesSnapshot.DependenciesByTargetFramework) { foreach ((string _, IDependency dependency) in targetedSnapshot.DependenciesWorld) { if (dependency.Visible && dependency.Caption.IndexOf(searchTerm, StringComparison.CurrentCultureIgnoreCase) != -1) { matchedDependencies.Add(dependency); } } } return(matchedDependencies); }
/// <summary> /// Does flat search among dependency world lists to find any dependencies that match search criteria. /// </summary> private static HashSet <IDependency> SearchFlat(string searchTerm, IDependenciesSnapshot dependenciesSnapshot) { var matchedDependencies = new HashSet <IDependency>(); foreach ((ITargetFramework _, ITargetedDependenciesSnapshot targetedSnapshot) in dependenciesSnapshot.Targets) { foreach ((string _, IDependency dependency) in targetedSnapshot.DependenciesWorld) { if (dependency.Visible && dependency.Caption.ToLowerInvariant().Contains(searchTerm)) { matchedDependencies.Add(dependency); } } } return(matchedDependencies); }
private void ScheduleDependenciesUpdate(CancellationToken token = default) { _dependenciesUpdateScheduler.ScheduleAsyncTask(ct => { if (ct.IsCancellationRequested || IsDisposing || IsDisposed) { return(Task.FromCanceled(ct)); } IDependenciesSnapshot snapshot = _currentSnapshot; if (snapshot != null) { SnapshotChanged?.Invoke(this, new SnapshotChangedEventArgs(snapshot, ct)); } return(Task.CompletedTask); }, token); }
/// <inheritdoc /> public ITargetedDependenciesSnapshot GetSnapshot(IDependency dependency) { IDependenciesSnapshot snapshot = GetSnapshot(dependency.FullPath); if (snapshot == null) { return(null); } ITargetFramework targetFramework = _targetFrameworkProvider.GetNearestFramework( dependency.TargetFramework, snapshot.Targets.Keys); if (targetFramework == null) { return(null); } return(snapshot.Targets[targetFramework]); }
private void ScheduleDependenciesUpdate() { DependenciesUpdateScheduler.ScheduleAsyncTask((token) => { if (token.IsCancellationRequested || IsDisposing || IsDisposed) { return(Task.FromCanceled(token)); } IDependenciesSnapshot snapshot = CurrentSnapshot; if (snapshot == null) { return(Task.CompletedTask); } OnDependenciesSnapshotChanged(snapshot); return(Task.CompletedTask); }); }
private ITargetedDependenciesSnapshot GetSnapshot(IDependency dependency) { IDependenciesSnapshot snapshot = AggregateSnapshotProvider.GetSnapshotProvider(dependency.FullPath)?.CurrentSnapshot; if (snapshot == null) { return(null); } ITargetFramework targetFramework = TargetFrameworkProvider.GetNearestFramework( dependency.TargetFramework, snapshot.Targets.Keys); if (targetFramework == null) { return(null); } return(snapshot.Targets[targetFramework]); }
/// <summary> /// Gets a value indicating whether deleting a given set of items from the project, and optionally from disk, /// would be allowed. /// Note: CanRemove can be called several times since there two types of remove operations: /// - Remove is a command that can remove project tree items form the tree/project but not from disk. /// For that command requests deleteOptions has DeleteOptions.None flag. /// - Delete is a command that can remove project tree items and form project and from disk. /// For this command requests deleteOptions has DeleteOptions.DeleteFromStorage flag. /// We can potentially support only Remove command here, since we don't remove Dependencies form disk, /// thus we return false when DeleteOptions.DeleteFromStorage is provided. /// </summary> /// <param name="nodes">The nodes that should be deleted.</param> /// <param name="deleteOptions"> /// A value indicating whether the items should be deleted from disk as well as from the project file. /// </param> public override bool CanRemove(IImmutableSet <IProjectTree> nodes, DeleteOptions deleteOptions = DeleteOptions.None) { if (deleteOptions.HasFlag(DeleteOptions.DeleteFromStorage)) { return(false); } IDependenciesSnapshot snapshot = DependenciesSnapshotProvider.CurrentSnapshot; if (snapshot == null) { return(false); } bool canRemove = true; foreach (IProjectTree node in nodes) { if (!node.Flags.Contains(DependencyTreeFlags.SupportsRemove)) { canRemove = false; break; } string filePath = UnconfiguredProject.GetRelativePath(node.FilePath); if (string.IsNullOrEmpty(filePath)) { continue; } IDependency dependency = snapshot.FindDependency(filePath, topLevel: true); if (dependency == null || dependency.Implicit) { canRemove = false; break; } } return(canRemove); }
/// <summary> /// Does flat search among dependency world lists to find any dependencies that match search criteria. /// </summary> private HashSet <IDependency> SearchFlat( string projectPath, string searchTerm, IGraphContext graphContext, IDependenciesSnapshot dependenciesSnapshot) { var matchedDependencies = new HashSet <IDependency>(); foreach (var targetedSnapshot in dependenciesSnapshot.Targets) { foreach (var dependency in targetedSnapshot.Value.DependenciesWorld) { if (dependency.Value.Visible && dependency.Value.Caption.ToLowerInvariant().Contains(searchTerm)) { matchedDependencies.Add(dependency.Value); } } } return(matchedDependencies); }
private void OnDependenciesSnapshotChanged(object sender, SnapshotChangedEventArgs e) { IDependenciesSnapshot snapshot = e.Snapshot; if (snapshot == null) { return; } if (_tasksService.UnloadCancellationToken.IsCancellationRequested || e.Token.IsCancellationRequested) { return; } // Take the highest priority view provider IDependenciesTreeViewProvider viewProvider = _viewProviders.FirstOrDefault()?.Value; if (viewProvider == null) { return; } _ = SubmitTreeUpdateAsync( async(treeSnapshot, configuredProjectExports, cancellationToken) => { IProjectTree dependenciesNode = treeSnapshot.Value.Tree; if (!cancellationToken.IsCancellationRequested) { dependenciesNode = await viewProvider.BuildTreeAsync(dependenciesNode, snapshot, cancellationToken); await _treeTelemetryService.ObserveTreeUpdateCompletedAsync(snapshot.HasUnresolvedDependency); } // TODO We still are getting mismatched data sources and need to figure out better // way of merging, mute them for now and get to it in U1 return(new TreeUpdateResult(dependenciesNode)); }, _treeUpdateCancellationSeries.CreateNext(e.Token)); }
/// <summary> /// ProjectContextChanged gets fired everytime dependencies change for projects accross solution. /// ExpandedGraphContexts contain all nodes that we need to check for potential updates in their /// children dependencies. /// </summary> private void OnSnapshotChanged(object sender, SnapshotChangedEventArgs e) { IDependenciesSnapshot snapshot = e.Snapshot; if (snapshot == null) { return; } lock (_changedContextsQueueLock) { _changedContextsQueue[snapshot.ProjectPath] = e; // schedule new track changes request in the queue if (_trackChangesTask == null || _trackChangesTask.IsCompleted) { _trackChangesTask = RunTrackChangesAsync(); } else { _trackChangesTask = _trackChangesTask.ContinueWith(t => RunTrackChangesAsync(), TaskScheduler.Default); } } }