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;
}
}
}