Skip to content

Commit a2aa50e

Browse files
committed
Make CodeExplorer refresh safer by waiting for things pushed to the UI thread to finish
Otherwise, it is possible to cancel a parse and start a new one, while the UI is still updating.
1 parent 64e09bd commit a2aa50e

File tree

6 files changed

+16
-13
lines changed

6 files changed

+16
-13
lines changed

Rubberduck.CodeAnalysis/CodeMetrics/CodeMetricsAnalyst.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using Antlr4.Runtime.Tree;
2-
using Rubberduck.Parsing.Symbols;
32
using Rubberduck.Parsing.VBA;
43
using Rubberduck.VBEditor;
54
using System.Collections.Generic;
@@ -14,11 +13,11 @@ public class CodeMetricsAnalyst : ICodeMetricsAnalyst
1413
{
1514
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
1615

17-
private readonly CodeMetric[] metrics;
16+
private readonly CodeMetric[] _metrics;
1817

1918
public CodeMetricsAnalyst(IEnumerable<CodeMetric> supportedMetrics)
2019
{
21-
metrics = supportedMetrics.ToArray();
20+
_metrics = supportedMetrics.ToArray();
2221
}
2322

2423
public IEnumerable<ICodeMetricResult> GetMetrics(RubberduckParserState state)
@@ -45,7 +44,7 @@ public IEnumerable<ICodeMetricResult> ModuleResults(QualifiedModuleName moduleNa
4544

4645
private IEnumerable<ICodeMetricResult> TraverseModuleTree(IParseTree parseTree, DeclarationFinder declarationFinder, QualifiedModuleName moduleName)
4746
{
48-
var listeners = metrics.Select(metric => metric.TreeListener).ToList();
47+
var listeners = _metrics.Select(metric => metric.TreeListener).ToList();
4948
foreach (var l in listeners)
5049
{
5150
l.Reset();

Rubberduck.CodeAnalysis/CodeMetrics/NestingLevelMetric.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
using Antlr4.Runtime.Misc;
22
using Rubberduck.Parsing.Grammar;
33
using Rubberduck.Parsing.Symbols;
4-
using System;
54
using System.Collections.Generic;
65
using System.Diagnostics;
76
using System.Linq;
8-
using System.Text;
9-
using System.Threading.Tasks;
107

118
namespace Rubberduck.CodeAnalysis.CodeMetrics
129
{

Rubberduck.Core/CodeAnalysis/CodeMetrics/CodeMetricsViewModel.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ private void Synchronize(IEnumerable<Declaration> declarations)
6565
var metricResults = _analyst.GetMetrics(_state);
6666
_resultsByDeclaration = metricResults.GroupBy(r => r.Declaration).ToDictionary(g => g.Key, g => g.ToList());
6767

68-
_uiDispatcher.Invoke(() =>
68+
//We have to wait for the task to guarantee that no new parse starts invalidating all cached components.
69+
_uiDispatcher.StartTask(() =>
6970
{
7071
var updates = declarations.ToList();
7172
var existing = Projects.OfType<CodeExplorerProjectViewModel>().ToList();
@@ -86,7 +87,7 @@ private void Synchronize(IEnumerable<Declaration> declarations)
8687
var model = new CodeExplorerProjectViewModel(project, ref updates, _state, _vbe, false);
8788
Projects.Add(model);
8889
}
89-
});
90+
}).Wait();
9091
}
9192

9293
private ICodeExplorerNode _selectedItem;

Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerViewModel.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,8 @@ private void HandleStateChanged(object sender, ParserStateEventArgs e)
231231
if (e.State == ParserState.Ready)
232232
{
233233
// Finished up resolving references, so we can now update the reference nodes.
234-
_uiDispatcher.Invoke(() =>
234+
//We have to wait for the task to guarantee that no new parse starts invalidating all cached components.
235+
_uiDispatcher.StartTask(() =>
235236
{
236237
var referenceFolders = Projects.SelectMany(node =>
237238
node.Children.OfType<CodeExplorerReferenceFolderViewModel>());
@@ -243,7 +244,7 @@ private void HandleStateChanged(object sender, ParserStateEventArgs e)
243244
Unparsed = !Projects.Any();
244245
IsBusy = false;
245246
ParserReady = true;
246-
});
247+
}).Wait();
247248
return;
248249
}
249250

@@ -264,7 +265,8 @@ private void HandleStateChanged(object sender, ParserStateEventArgs e)
264265
/// </param>
265266
private void Synchronize(IEnumerable<Declaration> declarations)
266267
{
267-
_uiDispatcher.Invoke(() =>
268+
//We have to wait for the task to guarantee that no new parse starts invalidating all cached components.
269+
_uiDispatcher.StartTask(() =>
268270
{
269271
var updates = declarations.ToList();
270272
var existing = Projects.OfType<CodeExplorerProjectViewModel>().ToList();
@@ -287,7 +289,7 @@ private void Synchronize(IEnumerable<Declaration> declarations)
287289
}
288290

289291
CanSearch = Projects.Any();
290-
});
292+
}).Wait();
291293
}
292294

293295
private void ParserState_ModuleStateChanged(object sender, ParseProgressEventArgs e)

RubberduckTests/CodeExplorer/CodeExplorerViewModelTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Linq;
44
using System.Threading;
5+
using System.Threading.Tasks;
56
using System.Windows.Forms;
67
using NUnit.Framework;
78
using Moq;
@@ -859,6 +860,7 @@ public void UnparsedSetToTrue_NoProjects()
859860
var dispatcher = new Mock<IUiDispatcher>();
860861

861862
dispatcher.Setup(m => m.Invoke(It.IsAny<Action>())).Callback((Action argument) => argument.Invoke());
863+
dispatcher.Setup(m => m.StartTask(It.IsAny<Action>(), It.IsAny<TaskCreationOptions>())).Returns((Action argument, TaskCreationOptions options) => Task.Factory.StartNew(argument.Invoke, options));
862864

863865
var viewModel = new CodeExplorerViewModel(state, null, null, null, dispatcher.Object, vbe.Object, null, new CodeExplorerSyncProvider(vbe.Object, state));
864866

RubberduckTests/CodeExplorer/MockedCodeExplorer.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Linq;
44
using System.Threading;
5+
using System.Threading.Tasks;
56
using System.Windows.Forms;
67
using NUnit.Framework;
78
using Moq;
@@ -49,6 +50,7 @@ public MockedCodeExplorer()
4950
.Returns(new UnitTestSettings(BindingMode.LateBinding,AssertMode.StrictAssert, true, true, false));
5051

5152
_uiDispatcher.Setup(m => m.Invoke(It.IsAny<Action>())).Callback((Action argument) => argument.Invoke());
53+
_uiDispatcher.Setup(m => m.StartTask(It.IsAny<Action>(), It.IsAny<TaskCreationOptions>())).Returns((Action argument, TaskCreationOptions options) => Task.Factory.StartNew(argument.Invoke, options));
5254

5355
SaveDialog = new Mock<ISaveFileDialog>();
5456
SaveDialog.Setup(o => o.OverwritePrompt);

0 commit comments

Comments
 (0)