From c2276b17cb196de44f1bbb42b91b000cde8fdc7f Mon Sep 17 00:00:00 2001 From: Gary Wilber Date: Wed, 30 Sep 2020 19:33:34 -0700 Subject: Increase library scan and metadata refresh speed --- MediaBrowser.Controller/Library/TaskMethods.cs | 133 +++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 MediaBrowser.Controller/Library/TaskMethods.cs (limited to 'MediaBrowser.Controller/Library/TaskMethods.cs') diff --git a/MediaBrowser.Controller/Library/TaskMethods.cs b/MediaBrowser.Controller/Library/TaskMethods.cs new file mode 100644 index 000000000..66bfbe0d9 --- /dev/null +++ b/MediaBrowser.Controller/Library/TaskMethods.cs @@ -0,0 +1,133 @@ +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; + } + } +} -- cgit v1.2.3