Skip to content
This repository was archived by the owner on Jun 21, 2023. It is now read-only.

Commit cf2bc71

Browse files
committed
Merge branch 'fixes/1493-using-ContinueWith' into feature/show-current-pr
2 parents ebbc6a6 + 16aabcd commit cf2bc71

File tree

2 files changed

+76
-41
lines changed

2 files changed

+76
-41
lines changed

src/GitHub.TeamFoundation.14/Services/VSGitExt.cs

Lines changed: 65 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,25 @@
11
using System;
22
using System.Linq;
3+
using System.Threading.Tasks;
34
using System.Collections.Generic;
45
using System.ComponentModel.Composition;
56
using GitHub.Models;
67
using GitHub.Services;
78
using GitHub.Logging;
9+
using GitHub.Helpers;
810
using GitHub.TeamFoundation.Services;
911
using Serilog;
1012
using Microsoft.VisualStudio.TeamFoundation.Git.Extensibility;
11-
using Task = System.Threading.Tasks.Task;
1213

1314
namespace GitHub.VisualStudio.Base
1415
{
1516
/// <summary>
1617
/// This service acts as an always available version of <see cref="IGitExt"/>.
1718
/// </summary>
19+
/// <remarks>
20+
/// Initialization for this service will be done asynchronously and the <see cref="IGitExt" /> service will be
21+
/// retrieved on the Main thread. This means the service can be constructed and subscribed to on a background thread.
22+
/// </remarks>
1823
[Export(typeof(IVSGitExt))]
1924
[PartCreationPolicy(CreationPolicy.Shared)]
2025
public class VSGitExt : IVSGitExt
@@ -24,7 +29,6 @@ public class VSGitExt : IVSGitExt
2429
readonly IGitHubServiceProvider serviceProvider;
2530
readonly IVSUIContext context;
2631
readonly ILocalRepositoryModelFactory repositoryFactory;
27-
readonly object refreshLock = new object();
2832

2933
IGitExt gitService;
3034
IReadOnlyList<ILocalRepositoryModel> activeRepositories;
@@ -41,52 +45,75 @@ public VSGitExt(IGitHubServiceProvider serviceProvider, IVSUIContextFactory fact
4145
this.repositoryFactory = repositoryFactory;
4246

4347
// The IGitExt service isn't available when a TFS based solution is opened directly.
44-
// It will become available when moving to a Git based solution (cause a UIContext event to fire).
48+
// It will become available when moving to a Git based solution (and cause a UIContext event to fire).
4549
context = factory.GetUIContext(new Guid(Guids.GitSccProviderId));
4650

47-
// Start with empty array until we have a change to initialize.
51+
// Start with empty array until we have a chance to initialize.
4852
ActiveRepositories = Array.Empty<ILocalRepositoryModel>();
4953

50-
if (context.IsActive && TryInitialize())
54+
PendingTasks = InitializeAsync();
55+
}
56+
57+
async Task InitializeAsync()
58+
{
59+
try
5160
{
52-
// Refresh ActiveRepositories on background thread so we don't delay startup.
53-
InitializeTask = Task.Run(() => RefreshActiveRepositories());
61+
if (!context.IsActive || !await TryInitialize())
62+
{
63+
// If we're not in the UIContext or TryInitialize fails, have another go when the UIContext changes.
64+
context.UIContextChanged += ContextChanged;
65+
log.Debug("VSGitExt will be initialized later");
66+
}
5467
}
55-
else
68+
catch (Exception e)
5669
{
57-
// If we're not in the UIContext or TryInitialize fails, have another go when the UIContext changes.
58-
context.UIContextChanged += ContextChanged;
59-
log.Debug("VSGitExt will be initialized later");
60-
InitializeTask = Task.CompletedTask;
70+
log.Error(e, "Initializing");
6171
}
6272
}
6373

6474
void ContextChanged(object sender, VSUIContextChangedEventArgs e)
6575
{
66-
// If we're in the UIContext and TryInitialize succeeds, we can stop listening for events.
67-
// NOTE: this event can fire with UIContext=true in a TFS solution (not just Git).
68-
if (e.Activated && TryInitialize())
76+
if (e.Activated)
77+
{
78+
PendingTasks = ContextChangedAsync();
79+
}
80+
}
81+
82+
async Task ContextChangedAsync()
83+
{
84+
try
85+
{
86+
// If we're in the UIContext and TryInitialize succeeds, we can stop listening for events.
87+
// NOTE: this event can fire with UIContext=true in a TFS solution (not just Git).
88+
if (await TryInitialize())
89+
{
90+
context.UIContextChanged -= ContextChanged;
91+
log.Debug("Initialized VSGitExt on UIContextChanged");
92+
}
93+
}
94+
catch (Exception e)
6995
{
70-
// Refresh ActiveRepositories on background thread so we don't delay UI context change.
71-
InitializeTask = Task.Run(() => RefreshActiveRepositories());
72-
context.UIContextChanged -= ContextChanged;
73-
log.Debug("Initialized VSGitExt on UIContextChanged");
96+
log.Error(e, "UIContextChanged");
7497
}
7598
}
7699

77-
bool TryInitialize()
100+
async Task<bool> TryInitialize()
78101
{
79-
gitService = serviceProvider.GetService<IGitExt>();
102+
gitService = await GetServiceAsync<IGitExt>();
80103
if (gitService != null)
81104
{
82105
gitService.PropertyChanged += (s, e) =>
83106
{
84107
if (e.PropertyName == nameof(gitService.ActiveRepositories))
85108
{
86-
RefreshActiveRepositories();
109+
// Execute tasks in sequence using thread pool (TaskScheduler.Default).
110+
PendingTasks = PendingTasks.ContinueWith(_ => RefreshActiveRepositories(), TaskScheduler.Default);
87111
}
88112
};
89113

114+
// Do this after we start listening so we don't miss an event.
115+
await Task.Run(() => RefreshActiveRepositories());
116+
90117
log.Debug("Found IGitExt service and initialized VSGitExt");
91118
return true;
92119
}
@@ -95,19 +122,23 @@ bool TryInitialize()
95122
return false;
96123
}
97124

125+
async Task<T> GetServiceAsync<T>() where T : class
126+
{
127+
// GetService must be called from the Main thread.
128+
await ThreadingHelper.SwitchToMainThreadAsync();
129+
return serviceProvider.GetService<T>();
130+
}
131+
98132
void RefreshActiveRepositories()
99133
{
100134
try
101135
{
102-
lock (refreshLock)
103-
{
104-
log.Debug(
105-
"IGitExt.ActiveRepositories (#{Id}) returned {Repositories}",
106-
gitService.GetHashCode(),
107-
gitService?.ActiveRepositories.Select(x => x.RepositoryPath));
136+
log.Debug(
137+
"IGitExt.ActiveRepositories (#{Id}) returned {Repositories}",
138+
gitService.GetHashCode(),
139+
gitService?.ActiveRepositories.Select(x => x.RepositoryPath));
108140

109-
ActiveRepositories = gitService?.ActiveRepositories.Select(x => repositoryFactory.Create(x.RepositoryPath)).ToList();
110-
}
141+
ActiveRepositories = gitService?.ActiveRepositories.Select(x => repositoryFactory.Create(x.RepositoryPath)).ToList();
111142
}
112143
catch (Exception e)
113144
{
@@ -136,6 +167,9 @@ private set
136167

137168
public event Action ActiveRepositoriesChanged;
138169

139-
public Task InitializeTask { get; private set; }
170+
/// <summary>
171+
/// Tasks that are pending execution on the thread pool.
172+
/// </summary>
173+
public Task PendingTasks { get; private set; }
140174
}
141175
}

test/UnitTests/GitHub.TeamFoundation/VSGitExtTests.cs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,10 @@
1111
using Microsoft.VisualStudio.TeamFoundation.Git.Extensibility;
1212
using System.Threading.Tasks;
1313
using System.Linq;
14-
using GitHub.TeamFoundation.Services;
1514

1615
public class VSGitExtTests
1716
{
18-
public class TheConstructor
17+
public class TheConstructor : TestBaseClass
1918
{
2019
[TestCase(true, 1)]
2120
[TestCase(false, 0)]
@@ -60,10 +59,10 @@ public void ActiveRepositories_ReadUsingThreadPoolThread()
6059
}
6160
}
6261

63-
public class TheActiveRepositoriesChangedEvent
62+
public class TheActiveRepositoriesChangedEvent : TestBaseClass
6463
{
6564
[Test]
66-
public void GitExtPropertyChangedEvent_ActiveRepositoriesChangedIsFired()
65+
public async Task GitExtPropertyChangedEvent_ActiveRepositoriesChangedIsFired()
6766
{
6867
var context = CreateVSUIContext(true);
6968
var gitExt = CreateGitExt();
@@ -75,6 +74,7 @@ public void GitExtPropertyChangedEvent_ActiveRepositoriesChangedIsFired()
7574
var eventArgs = new PropertyChangedEventArgs(nameof(gitExt.ActiveRepositories));
7675
gitExt.PropertyChanged += Raise.Event<PropertyChangedEventHandler>(gitExt, eventArgs);
7776

77+
await target.PendingTasks;
7878
Assert.That(wasFired, Is.True);
7979
}
8080

@@ -108,7 +108,7 @@ public void WhenUIContextChanged_ActiveRepositoriesChangedIsFired()
108108

109109
var eventArgs = new VSUIContextChangedEventArgs(true);
110110
context.UIContextChanged += Raise.Event<EventHandler<VSUIContextChangedEventArgs>>(context, eventArgs);
111-
target.InitializeTask.Wait();
111+
target.PendingTasks.Wait();
112112

113113
Assert.That(wasFired, Is.True);
114114
}
@@ -125,13 +125,13 @@ public void WhenUIContextChanged_FiredUsingThreadPoolThread()
125125

126126
var eventArgs = new VSUIContextChangedEventArgs(true);
127127
context.UIContextChanged += Raise.Event<EventHandler<VSUIContextChangedEventArgs>>(context, eventArgs);
128-
target.InitializeTask.Wait();
128+
target.PendingTasks.Wait();
129129

130130
Assert.That(threadPool, Is.True);
131131
}
132132
}
133133

134-
public class TheActiveRepositoriesProperty
134+
public class TheActiveRepositoriesProperty : TestBaseClass
135135
{
136136
[Test]
137137
public void SccProviderContextNotActive_IsEmpty()
@@ -150,7 +150,7 @@ public void SccProviderContextIsActive_InitializeWithActiveRepositories()
150150
var context = CreateVSUIContext(true);
151151
var gitExt = CreateGitExt(new[] { repoPath });
152152
var target = CreateVSGitExt(context, gitExt, repoFactory: repoFactory);
153-
target.InitializeTask.Wait();
153+
target.PendingTasks.Wait();
154154

155155
var activeRepositories = target.ActiveRepositories;
156156

@@ -167,7 +167,7 @@ public void ExceptionRefreshingRepositories_ReturnsEmptyList()
167167
var context = CreateVSUIContext(true);
168168
var gitExt = CreateGitExt(new[] { repoPath });
169169
var target = CreateVSGitExt(context, gitExt, repoFactory: repoFactory);
170-
target.InitializeTask.Wait();
170+
target.PendingTasks.Wait();
171171

172172
var activeRepositories = target.ActiveRepositories;
173173

@@ -188,6 +188,7 @@ public async Task ActiveRepositoriesChangedOrderingShouldBeCorrectAcrossThreads(
188188
var task2 = Task.Run(() => gitExt.ActiveRepositories = activeRepositories2);
189189

190190
await Task.WhenAll(task1, task2);
191+
await target.PendingTasks;
191192

192193
Assert.That(target.ActiveRepositories.Single().LocalPath, Is.EqualTo("repo2"));
193194
}
@@ -219,7 +220,7 @@ static VSGitExt CreateVSGitExt(IVSUIContext context = null, IGitExt gitExt = nul
219220
sp.GetService<IVSUIContextFactory>().Returns(factory);
220221
sp.GetService<IGitExt>().Returns(gitExt);
221222
var vsGitExt = new VSGitExt(sp, factory, repoFactory);
222-
vsGitExt.InitializeTask.Wait();
223+
vsGitExt.PendingTasks.Wait();
223224
return vsGitExt;
224225
}
225226

0 commit comments

Comments
 (0)