File: Notification\AbstractGlobalOperationNotificationService.cs
Web Access
Project: src\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Notification;
 
internal abstract partial class AbstractGlobalOperationNotificationService : IGlobalOperationNotificationService
{
    private readonly object _gate = new();
 
    private readonly HashSet<IDisposable> _registrations = [];
    private readonly HashSet<string> _operations = [];
 
    private readonly TaskQueue _eventQueue;
 
    public event EventHandler? Started;
    public event EventHandler? Stopped;
 
    protected AbstractGlobalOperationNotificationService(
        IAsynchronousOperationListenerProvider listenerProvider)
    {
        _eventQueue = new TaskQueue(listenerProvider.GetListener(FeatureAttribute.GlobalOperation), TaskScheduler.Default);
    }
 
    ~AbstractGlobalOperationNotificationService()
    {
        if (!Environment.HasShutdownStarted)
        {
            Contract.ThrowIfFalse(_registrations.Count == 0);
            Contract.ThrowIfFalse(_operations.Count == 0, $"Non-disposed operations: {string.Join(", ", _operations)}");
        }
    }
 
    private void RaiseGlobalOperationStarted()
    {
        var started = this.Started;
        if (started != null)
            _eventQueue.ScheduleTask(nameof(RaiseGlobalOperationStarted), () => this.Started?.Invoke(this, EventArgs.Empty), CancellationToken.None);
    }
 
    private void RaiseGlobalOperationStopped()
    {
        var stopped = this.Stopped;
        if (stopped != null)
            _eventQueue.ScheduleTask(nameof(RaiseGlobalOperationStopped), () => this.Stopped?.Invoke(this, EventArgs.Empty), CancellationToken.None);
    }
 
    public IDisposable Start(string operation)
    {
        lock (_gate)
        {
            // create new registration
            var registration = new GlobalOperationRegistration(this);
 
            // states
            _registrations.Add(registration);
            _operations.Add(operation);
 
            // the very first one
            if (_registrations.Count == 1)
            {
                Contract.ThrowIfFalse(_operations.Count == 1);
                RaiseGlobalOperationStarted();
            }
 
            return registration;
        }
    }
 
    private void Done(GlobalOperationRegistration registration)
    {
        lock (_gate)
        {
            var result = _registrations.Remove(registration);
            Contract.ThrowIfFalse(result);
 
            if (_registrations.Count == 0)
            {
                _operations.Clear();
                RaiseGlobalOperationStopped();
            }
        }
    }
}