using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Configuration; namespace MediaBrowser.Controller.Library { /// /// Helper methods for running tasks concurrently. /// public static class TaskMethods { private static readonly int _processorCount = Environment.ProcessorCount; private static readonly ConcurrentDictionary _sharedThrottlers = new ConcurrentDictionary(); /// /// Throttle id for sharing a concurrency limit. /// public enum SharedThrottleId { /// /// Library scan fan out /// ScanFanout, /// /// Refresh metadata /// RefreshMetadata, } /// /// Gets or sets the configuration manager. /// public static IServerConfigurationManager ConfigurationManager { get; set; } /// /// Similiar to Task.WhenAll but only allows running a certain amount of tasks at the same time. /// /// The throttle id. Multiple calls to this method with the same throttle id will share a concurrency limit. /// List of actions to run. /// The cancellation token. /// A representing the result of the asynchronous operation. public static async Task WhenAllThrottled(SharedThrottleId throttleId, IEnumerable> actions, CancellationToken cancellationToken) { var taskThrottler = throttleId == SharedThrottleId.ScanFanout ? new SemaphoreSlim(GetConcurrencyLimit(throttleId)) : _sharedThrottlers.GetOrAdd(throttleId, id => new SemaphoreSlim(GetConcurrencyLimit(id))); try { var tasks = new List(); foreach (var action in actions) { await taskThrottler.WaitAsync(cancellationToken).ConfigureAwait(false); tasks.Add(Task.Run(async () => { try { await action().ConfigureAwait(false); } finally { taskThrottler.Release(); } })); } await Task.WhenAll(tasks).ConfigureAwait(false); } finally { if (throttleId == SharedThrottleId.ScanFanout) { taskThrottler.Dispose(); } } } /// /// Runs a task within a given throttler. /// /// The throttle id. Multiple calls to this method with the same throttle id will share a concurrency limit. /// The action to run. /// The cancellation token. /// A representing the result of the asynchronous operation. public static async Task RunThrottled(SharedThrottleId throttleId, Func action, CancellationToken cancellationToken) { if (throttleId == SharedThrottleId.ScanFanout) { // just await the task instead throw new InvalidOperationException("Invalid throttle id"); } var taskThrottler = _sharedThrottlers.GetOrAdd(throttleId, id => new SemaphoreSlim(GetConcurrencyLimit(id))); await taskThrottler.WaitAsync(cancellationToken).ConfigureAwait(false); try { await action().ConfigureAwait(false); } finally { taskThrottler.Release(); } } /// /// Get the concurrency limit for the given throttle id. /// /// The throttle id. /// The concurrency limit. private static int GetConcurrencyLimit(SharedThrottleId throttleId) { var concurrency = throttleId == SharedThrottleId.RefreshMetadata ? ConfigurationManager.Configuration.LibraryMetadataRefreshConcurrency : ConfigurationManager.Configuration.LibraryScanFanoutConcurrency; if (concurrency <= 0) { concurrency = _processorCount; } return concurrency; } } }