/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/VisualStudio/Core/Def/Interop/CleanableWeakComHandleTable.cs
213 строк
7 KB
Cyrus Najmabadi
Use auto properties in Microsoft.VisualStudio.LanguageServices
02 авг 2024, 23:01
02 авг 2024, 23:01
90163c2
Код
Авторство
О чём код?
// 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. #nullable disable using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.Interop; /// <summary> /// Special collection for storing a table of COM objects weakly that provides /// logic for cleaning up dead references in a time-sliced way. Public members of this /// collection are affinitized to the foreground thread. /// </summary> internal sealed class CleanableWeakComHandleTable<TKey, TValue> where TValue : class { private const int DefaultCleanUpThreshold = 25; private static readonly TimeSpan s_defaultCleanUpTimeSlice = TimeSpan.FromMilliseconds(15); private readonly Dictionary<TKey, WeakComHandle<TValue, TValue>> _table; private readonly HashSet<TKey> _deadKeySet; private readonly IThreadingContext _threadingContext; /// <summary> /// The upper limit of items that the collection will store before clean up is recommended. /// </summary> public int CleanUpThreshold { get; } /// <summary> /// The amount of time that can pass during clean up it returns. /// </summary> public TimeSpan CleanUpTimeSlice { get; } private int _itemsAddedSinceLastCleanUp; public bool NeedsCleanUp { get; private set; } public CleanableWeakComHandleTable(IThreadingContext threadingContext, int? cleanUpThreshold = null, TimeSpan? cleanUpTimeSlice = null) { _table = []; _deadKeySet = []; _threadingContext = threadingContext; CleanUpThreshold = cleanUpThreshold ?? DefaultCleanUpThreshold; CleanUpTimeSlice = cleanUpTimeSlice ?? s_defaultCleanUpTimeSlice; } /// <summary> /// Cleans up references to dead objects in the table. This operation will yield to other foreground operations /// any time execution exceeds <see cref="CleanUpTimeSlice"/>. /// </summary> public async Task CleanUpDeadObjectsAsync(IAsynchronousOperationListener listener) { using var _ = listener.BeginAsyncOperation(nameof(CleanUpDeadObjectsAsync)); Debug.Assert(_threadingContext.JoinableTaskContext.IsOnMainThread, "This method is optimized for cases where calls do not yield before checking _needsCleanUp."); await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(_threadingContext.DisposalToken); if (!NeedsCleanUp) { return; } // Immediately mark as not needing cleanup; this operation will clean up the table by the time it returns. NeedsCleanUp = false; var timeSlice = new TimeSlice(CleanUpTimeSlice); await CollectDeadKeysAsync().ConfigureAwait(true); await RemoveDeadKeysAsync().ConfigureAwait(true); return; // Local functions async Task CollectDeadKeysAsync() { // This method returns after making a complete pass enumerating the elements of _table without finding // any entries that are not alive. If a pass exceeds the allowed time slice after finding one or more // dead entries, the pass yields before processing the elements found so far and restarting the // enumeration. // // ⚠ This method may interleave with other asynchronous calls to CleanUpDeadObjectsAsync. var cleanUpEnumerator = _table.GetEnumerator(); while (cleanUpEnumerator.MoveNext()) { var pair = cleanUpEnumerator.Current; if (!pair.Value.IsAlive()) { _deadKeySet.Add(pair.Key); if (timeSlice.IsOver) { // Yield before processing items found so far. await ResetTimeSliceAsync().ConfigureAwait(true); // Process items found prior to exceeding the time slice. Due to interleaving, it is // possible for this call to process items found by another asynchronous call to // CollectDeadKeysAsync, or for another asynchronous call to RemoveDeadKeysAsync to process // all items prior to this call. await RemoveDeadKeysAsync().ConfigureAwait(true); // Obtain a new enumerator since the previous one may be invalidated. cleanUpEnumerator = _table.GetEnumerator(); } } } } async Task RemoveDeadKeysAsync() { while (_deadKeySet.Count > 0) { // Fully process one item from _deadKeySet before the possibility of yielding var key = _deadKeySet.First(); _deadKeySet.Remove(key); Debug.Assert(_table.ContainsKey(key), "Key not found in table."); _table.Remove(key); if (timeSlice.IsOver) { await ResetTimeSliceAsync().ConfigureAwait(true); } } } async Task ResetTimeSliceAsync() { await listener.Delay(DelayTimeSpan.NearImmediate, _threadingContext.DisposalToken).ConfigureAwait(true); timeSlice = new TimeSlice(CleanUpTimeSlice); } } public void Add(TKey key, TValue value) { _threadingContext.ThrowIfNotOnUIThread(); if (value == null) { throw new ArgumentNullException(nameof(value)); } if (_table.ContainsKey(key)) { throw new InvalidOperationException($"Key already exists in table: {(key != null ? key.ToString() : "<null>")}."); } _itemsAddedSinceLastCleanUp++; if (_itemsAddedSinceLastCleanUp >= CleanUpThreshold) { NeedsCleanUp = true; _itemsAddedSinceLastCleanUp = 0; } _table.Add(key, new WeakComHandle<TValue, TValue>(value)); } public TValue Remove(TKey key) { _threadingContext.ThrowIfNotOnUIThread(); _deadKeySet.Remove(key); if (_table.TryGetValue(key, out var handle)) { _table.Remove(key); return handle.ComAggregateObject; } return null; } public bool ContainsKey(TKey key) { _threadingContext.ThrowIfNotOnUIThread(); return _table.ContainsKey(key); } public bool TryGetValue(TKey key, out TValue value) { _threadingContext.ThrowIfNotOnUIThread(); if (_table.TryGetValue(key, out var handle)) { value = handle.ComAggregateObject; return value != null; } value = null; return false; } public IEnumerable<TValue> Values { get { foreach (var keyValuePair in _table) { yield return keyValuePair.Value.ComAggregateObject; } } } }