private async Task PromoteTaskToActiveAsync(BackgroundRestoreOperation restoreOperation, CancellationToken token) { var pendingTask = restoreOperation.Task; int attempt = 0; for (var retry = true; retry && !token.IsCancellationRequested && attempt != PromoteAttemptsLimit; attempt++) { // Grab local copy of active task var activeTask = _activeRestoreTask; // Await for the completion of the active *unbound* task var cancelTcs = new TaskCompletionSource <bool>(); using (var ctr = token.Register(() => cancelTcs.TrySetCanceled())) { await Task.WhenAny(activeTask, cancelTcs.Task); } // Try replacing active task with the new one. // Retry from the beginning if the active task has changed. retry = Interlocked.CompareExchange( ref _activeRestoreTask, pendingTask, activeTask) != activeTask; } if (attempt == PromoteAttemptsLimit) { throw new InvalidOperationException("Failed promoting pending task."); } }
public async Task <bool> RestoreAsync(SolutionRestoreRequest request, CancellationToken token) { // Signal that restore is running _isCompleteEvent.Reset(); try { using (_joinableCollection.Join()) { await StartInitializationAsync(); using (var restoreOperation = new BackgroundRestoreOperation()) { await PromoteTaskToActiveAsync(restoreOperation, token); var result = await ProcessRestoreRequestAsync(restoreOperation, request, token); return(result); } } } finally { // Signal that restore has been completed. _isCompleteEvent.Set(); } }
private async Task <bool> ProcessRestoreRequestAsync( BackgroundRestoreOperation restoreOperation, SolutionRestoreRequest request, CancellationToken token) { // Start the restore job in a separate task on a background thread // it will switch into main thread when necessary. var joinableTask = _joinableFactory.RunAsync( () => StartRestoreJobAsync(request, token)); var continuation = joinableTask .Task .ContinueWith(t => restoreOperation.ContinuationAction(t)); return(await joinableTask); }
public async Task <bool> RestoreAsync(SolutionRestoreRequest request, CancellationToken token) { using (_joinableCollection.Join()) { // Initialize if not already done. await InitializeAsync(); using (var restoreOperation = new BackgroundRestoreOperation()) { await PromoteTaskToActiveAsync(restoreOperation, token); var result = await ProcessRestoreRequestAsync(restoreOperation, request, token); return(result); } } }
private void Reset(bool isDisposing = false) { // Make sure worker restore operation is cancelled _workerCts?.Cancel(); if (_backgroundJobRunner?.IsValueCreated == true) { // Await completion of the background work _joinableFactory.Run( async() => { // Do not block VS forever // After the specified delay the task will disjoin. await _backgroundJobRunner.GetValueAsync().WithTimeout(TimeSpan.FromSeconds(60)); }, JoinableTaskCreationOptions.LongRunning); } _pendingRestore?.Dispose(); _workerCts?.Dispose(); if (_pendingRequests?.IsValueCreated == true) { _pendingRequests.Value.Dispose(); } if (!isDisposing) { _solutionLoadedEvent.Reset(); _workerCts = new CancellationTokenSource(); _backgroundJobRunner = new AsyncLazy <bool>( () => StartBackgroundJobRunnerAsync(_workerCts.Token), _joinableFactory); _pendingRequests = new Lazy <BlockingCollection <SolutionRestoreRequest> >( () => new BlockingCollection <SolutionRestoreRequest>(RequestQueueLimit)); _pendingRestore = new BackgroundRestoreOperation(); _activeRestoreTask = Task.FromResult(true); _restoreJobContext = new SolutionRestoreJobContext(); } }
public bool Restore(SolutionRestoreRequest request) { return(_joinableFactory.Run( async() => { using (_joinableCollection.Join()) { // Initialize if not already done. await InitializeAsync(); using (var restoreOperation = new BackgroundRestoreOperation()) { await PromoteTaskToActiveAsync(restoreOperation, _workerCts.Token); var result = await ProcessRestoreRequestAsync(restoreOperation, request, _workerCts.Token); return result; } } }, JoinableTaskCreationOptions.LongRunning)); }
private async Task <bool> ProcessRestoreRequestAsync( BackgroundRestoreOperation restoreOperation, SolutionRestoreRequest request, CancellationToken token) { // if the request is implicit & this is the first restore, assume we are restoring due to a solution load. var isSolutionLoadRestore = _isFirstRestore && request.RestoreSource == RestoreOperationSource.Implicit; _isFirstRestore = false; // Start the restore job in a separate task on a background thread // it will switch into main thread when necessary. var joinableTask = JoinableTaskFactory.RunAsync( () => StartRestoreJobAsync(request, isSolutionLoadRestore, token)); var continuation = joinableTask .Task .ContinueWith(t => restoreOperation.ContinuationAction(t, JoinableTaskFactory)); return(await joinableTask); }
public async Task <bool> ScheduleRestoreAsync( SolutionRestoreRequest request, CancellationToken token) { if (token.IsCancellationRequested) { return(false); } // Reset to signal that a restore is in progress. // This sets IsRunning to true _isCompleteEvent.Reset(); try { await StartInitializationAsync(); BackgroundRestoreOperation pendingRestore = _pendingRestore; // lock _pendingRequests to figure out if we need to start a new background job for restore // or if there is already one running which will also take care of current request. lock (_lockPendingRequestsObj) { var shouldStartNewBGJobRunner = true; // check if there are already pending restore request or active restore task // then don't initiate a new background job runner. if (_pendingRequests.Value.Count > 0 || IsBusy) { shouldStartNewBGJobRunner = false; } else if (_lockService.Value.IsLockHeld && _lockService.Value.LockCount > 0) { // when restore is not running but NuGet lock is still held for the current async operation, // then it means other NuGet operation like Install or Update are in progress which will // take care of running restore for appropriate projects so skipping auto restore in that case. return(true); } AsyncLazy <bool> backgroundJobRunner = _backgroundJobRunner; if (backgroundJobRunner == null) { shouldStartNewBGJobRunner = true; } else if (backgroundJobRunner.IsValueCreated && backgroundJobRunner.IsValueFactoryCompleted) { Task <bool> valueTask = backgroundJobRunner.GetValueAsync(); if (valueTask.IsFaulted || valueTask.IsCanceled) { shouldStartNewBGJobRunner = true; } } // on-board request onto pending restore operation _pendingRequests.Value.TryAdd(request); // When there is no current background restore job running, then start a new one. // Otherwise, the current request will await the existing job to be completed. if (shouldStartNewBGJobRunner) { _backgroundJobRunner = new AsyncLazy <bool>( () => StartBackgroundJobRunnerAsync(_workerCts.Token), JoinableTaskFactory); } } try { using (_joinableCollection.Join()) { // Await completion of the requested restore operation or // completion of the current job runner. // The caller will be unblocked immediately upon // cancellation request via provided token. return(await await Task .WhenAny( pendingRestore.Task, _backgroundJobRunner.GetValueAsync()) .WithCancellation(token)); } } catch (OperationCanceledException) when(token.IsCancellationRequested) { return(false); } catch (Exception e) { Logger.LogError(e.ToString()); return(false); } } finally { // Signal that all pending operations are complete. _isCompleteEvent.Set(); } }