private Task <T> ExecuteWithinLockAsync <T>(Func <Task <T> > task)
 {
     return(_gate.ExecuteWithinLockAsync(JoinableCollection, JoinableFactory, task));
 }
        private async Task UpdateProjectContextAndSubscriptionsAsync()
        {
            // Prevent concurrent project context updates.
            AggregateCrossTargetProjectContext newProjectContext
                = await _contextUpdateGate.ExecuteWithinLockAsync(JoinableCollection, JoinableFactory, TryUpdateCurrentAggregateProjectContextAsync);

            if (newProjectContext != null)
            {
                // Dispose existing subscriptions.
                DisposeAndClearSubscriptions();

                // Add subscriptions for the configured projects in the new project context.
                await AddSubscriptionsAsync(newProjectContext);
            }

            return;

            async Task <AggregateCrossTargetProjectContext?> TryUpdateCurrentAggregateProjectContextAsync()
            {
                AggregateCrossTargetProjectContext?previousContext = _currentAggregateProjectContext;

                // Check if we have already computed the project context.
                if (previousContext != null)
                {
                    // For non-cross targeting projects, we can use the current project context if the TargetFramework hasn't changed.
                    // For cross-targeting projects, we need to verify that the current project context matches latest frameworks targeted by the project.
                    // If not, we create a new one and dispose the current one.
                    ConfigurationGeneral projectProperties = await _commonServices.ActiveConfiguredProjectProperties.GetConfigurationGeneralPropertiesAsync();

                    if (!previousContext.IsCrossTargeting)
                    {
                        string newTargetFrameworkName = (string)await projectProperties.TargetFramework.GetValueAsync();

                        ITargetFramework?newTargetFramework = _targetFrameworkProvider.GetTargetFramework(newTargetFrameworkName);
                        if (previousContext.ActiveTargetFramework.Equals(newTargetFramework))
                        {
                            // No change
                            return(null);
                        }
                    }
                    else
                    {
                        // Check if the current project context is up-to-date for the current active and known project configurations.
                        ProjectConfiguration activeProjectConfiguration = _commonServices.ActiveConfiguredProject.ProjectConfiguration;
                        IImmutableSet <ProjectConfiguration> knownProjectConfigurations = await _commonServices.Project.Services.ProjectConfigurationsService.GetKnownProjectConfigurationsAsync();

                        if (knownProjectConfigurations.All(c => c.IsCrossTargeting()) &&
                            HasMatchingTargetFrameworks(previousContext, activeProjectConfiguration, knownProjectConfigurations))
                        {
                            // No change
                            return(null);
                        }
                    }
                }

                // Force refresh the CPS active project configuration (needs UI thread).
                await _commonServices.ThreadingService.SwitchToUIThread();

                await _activeProjectConfigurationRefreshService.RefreshActiveProjectConfigurationAsync();

                // Create new project context.
                AggregateCrossTargetProjectContext newContext = await _contextProvider.Value.CreateProjectContextAsync();

                _currentAggregateProjectContext = newContext;

                OnAggregateContextChanged(previousContext, newContext);

                return(newContext);
            }

            bool HasMatchingTargetFrameworks(
                AggregateCrossTargetProjectContext previousContext,
                ProjectConfiguration activeProjectConfiguration,
                IReadOnlyCollection <ProjectConfiguration> knownProjectConfigurations)
            {
                Assumes.True(activeProjectConfiguration.IsCrossTargeting());

                ITargetFramework?activeTargetFramework = _targetFrameworkProvider.GetTargetFramework(activeProjectConfiguration.Dimensions[ConfigurationGeneral.TargetFrameworkProperty]);

                if (!previousContext.ActiveTargetFramework.Equals(activeTargetFramework))
                {
                    // Active target framework is different.
                    return(false);
                }

                var targetFrameworkMonikers = knownProjectConfigurations
                                              .Select(c => c.Dimensions[ConfigurationGeneral.TargetFrameworkProperty])
                                              .Distinct()
                                              .ToList();

                if (targetFrameworkMonikers.Count != previousContext.TargetFrameworks.Length)
                {
                    // Different number of target frameworks.
                    return(false);
                }

                foreach (string targetFrameworkMoniker in targetFrameworkMonikers)
                {
                    ITargetFramework?targetFramework = _targetFrameworkProvider.GetTargetFramework(targetFrameworkMoniker);

                    if (targetFramework == null || !previousContext.TargetFrameworks.Contains(targetFramework))
                    {
                        // Differing TargetFramework
                        return(false);
                    }
                }

                return(true);
            }

            void OnAggregateContextChanged(
                AggregateCrossTargetProjectContext?oldContext,
                AggregateCrossTargetProjectContext newContext)
            {
                if (oldContext == null)
                {
                    // all new rules will be sent to new context, we don't need to clean up anything
                    return;
                }

                var targetsToClean = new HashSet <ITargetFramework>();

                ImmutableArray <ITargetFramework> oldTargets = oldContext.TargetFrameworks;

                if (newContext == null)
                {
                    targetsToClean.AddRange(oldTargets);
                }
                else
                {
                    ImmutableArray <ITargetFramework> newTargets = newContext.TargetFrameworks;

                    targetsToClean.AddRange(oldTargets.Except(newTargets));
                }

                if (targetsToClean.Count != 0)
                {
                    TryUpdateSnapshot(snapshot => snapshot.RemoveTargets(targetsToClean));
                }
            }
        }