11using System ;
22using System . Linq ;
3+ using System . Threading . Tasks ;
34using System . Collections . Generic ;
45using System . ComponentModel . Composition ;
56using GitHub . Models ;
67using GitHub . Services ;
78using GitHub . Logging ;
9+ using GitHub . Helpers ;
810using GitHub . TeamFoundation . Services ;
911using Serilog ;
1012using Microsoft . VisualStudio . TeamFoundation . Git . Extensibility ;
11- using Task = System . Threading . Tasks . Task ;
1213
1314namespace 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}
0 commit comments