web-dev-qa-db-fra.com

Limiter le nombre de threads parallèles en C #

J'écris un programme C # pour générer et télécharger un demi-million de fichiers via FTP. Je veux traiter 4 fichiers en parallèle car la machine a 4 cœurs et la génération de fichier prend beaucoup plus de temps. Est-il possible de convertir l'exemple Powershell suivant en C #? Ou existe-t-il un meilleur framework tel que le framework Actor en C # (comme F # MailboxProcessor)?

Exemple de Powershell

$maxConcurrentJobs = 3;

# Read the input and queue it up
$jobInput = get-content .\input.txt
$queue = [System.Collections.Queue]::Synchronized( (New-Object System.Collections.Queue) )
foreach($item in $jobInput)
{
    $queue.Enqueue($item)
}

# Function that pops input off the queue and starts a job with it
function RunJobFromQueue
{
    if( $queue.Count -gt 0)
    {
        $j = Start-Job -ScriptBlock {param($x); Get-WinEvent -LogName $x} -ArgumentList $queue.Dequeue()
        Register-ObjectEvent -InputObject $j -EventName StateChanged -Action { RunJobFromQueue; Unregister-Event $eventsubscriber.SourceIdentifier; Remove-Job $eventsubscriber.SourceIdentifier } | Out-Null
    }
}

# Start up to the max number of concurrent jobs
# Each job will take care of running the rest
for( $i = 0; $i -lt $maxConcurrentJobs; $i++ )
{
    RunJobFromQueue
}

Mettre à jour:
La connexion au serveur FTP distant peut être lente, aussi je veux limiter le traitement du téléchargement FTP. 

22
ca9163d9

En supposant que vous construisiez ceci avec la TPL, vous pouvez définir le paramètre ParallelOptions.MaxDegreesOfParallelism sur ce que vous souhaitez qu'il soit.

Parallel.For pour un exemple de code.

31
Austin Salonen

Task Parallel Library est votre ami ici. Voir this link qui décrit ce qui est disponible pour vous. En gros, le framework 4 vient avec, optimisant ces threads regroupés essentiellement en arrière-plan en fonction du nombre de processeurs sur la machine en cours d'exécution.

Peut-être quelque chose dans le genre de:

ParallelOptions options = new ParallelOptions();

options.MaxDegreeOfParallelism = 4;

Ensuite, dans votre boucle, quelque chose comme:

Parallel.Invoke(options,
 () => new WebClient().Upload("http://www.linqpad.net", "lp.html"),
 () => new WebClient().Upload("http://www.jaoo.dk", "jaoo.html"));
17
Jeb

Si vous utilisez .Net 4.0, vous pouvez utiliser la bibliothèque parallèle _

Si vous parcourez le demi-million de fichiers, vous pouvez "mettre en parallèle" l'itération en utilisant un Parallel Foreach, par exemple ou vous pouvez consulter PLinq . comparaison entre les deux

5
Giorgio Minardi

En gros, vous allez vouloir créer une action ou une tâche pour chaque fichier à télécharger, les placer dans une liste, puis traiter cette liste, en limitant le nombre pouvant être traité en parallèle.

Mon article de blog montre comment faire cela à la fois avec les tâches et avec les actions, et fournit un exemple de projet que vous pouvez télécharger et exécuter pour voir les deux en action. 

Avec actions

Si vous utilisez des actions, vous pouvez utiliser la fonction intégrée .Net Parallel.Invoke. Ici, nous le limitons à un maximum de 4 threads en parallèle.

var listOfActions = new List<Action>();
foreach (var file in files)
{
    var localFile = file;
    // Note that we create the Task here, but do not start it.
    listOfTasks.Add(new Task(() => UploadFile(localFile)));
}

var options = new ParallelOptions {MaxDegreeOfParallelism = 4};
Parallel.Invoke(options, listOfActions.ToArray());

Cependant, cette option ne prend pas en charge async, et je suppose que votre fonction FileUpload le sera, vous voudrez peut-être utiliser l'exemple de tâche ci-dessous.

Avec des tâches

Avec les tâches, il n'y a pas de fonction intégrée. Cependant, vous pouvez utiliser celui que je fournis sur mon blog.

    /// <summary>
    /// Starts the given tasks and waits for them to complete. This will run, at most, the specified number of tasks in parallel.
    /// <para>NOTE: If one of the given tasks has already been started, an exception will be thrown.</para>
    /// </summary>
    /// <param name="tasksToRun">The tasks to run.</param>
    /// <param name="maxTasksToRunInParallel">The maximum number of tasks to run in parallel.</param>
    /// <param name="cancellationToken">The cancellation token.</param>
    public static async Task StartAndWaitAllThrottledAsync(IEnumerable<Task> tasksToRun, int maxTasksToRunInParallel, CancellationToken cancellationToken = new CancellationToken())
    {
        await StartAndWaitAllThrottledAsync(tasksToRun, maxTasksToRunInParallel, -1, cancellationToken);
    }

    /// <summary>
    /// Starts the given tasks and waits for them to complete. This will run the specified number of tasks in parallel.
    /// <para>NOTE: If a timeout is reached before the Task completes, another Task may be started, potentially running more than the specified maximum allowed.</para>
    /// <para>NOTE: If one of the given tasks has already been started, an exception will be thrown.</para>
    /// </summary>
    /// <param name="tasksToRun">The tasks to run.</param>
    /// <param name="maxTasksToRunInParallel">The maximum number of tasks to run in parallel.</param>
    /// <param name="timeoutInMilliseconds">The maximum milliseconds we should allow the max tasks to run in parallel before allowing another task to start. Specify -1 to wait indefinitely.</param>
    /// <param name="cancellationToken">The cancellation token.</param>
    public static async Task StartAndWaitAllThrottledAsync(IEnumerable<Task> tasksToRun, int maxTasksToRunInParallel, int timeoutInMilliseconds, CancellationToken cancellationToken = new CancellationToken())
    {
        // Convert to a list of tasks so that we don't enumerate over it multiple times needlessly.
        var tasks = tasksToRun.ToList();

        using (var throttler = new SemaphoreSlim(maxTasksToRunInParallel))
        {
            var postTaskTasks = new List<Task>();

            // Have each task notify the throttler when it completes so that it decrements the number of tasks currently running.
            tasks.ForEach(t => postTaskTasks.Add(t.ContinueWith(tsk => throttler.Release())));

            // Start running each task.
            foreach (var task in tasks)
            {
                // Increment the number of tasks currently running and wait if too many are running.
                await throttler.WaitAsync(timeoutInMilliseconds, cancellationToken);

                cancellationToken.ThrowIfCancellationRequested();
                task.Start();
            }

            // Wait for all of the provided tasks to complete.
            // We wait on the list of "post" tasks instead of the original tasks, otherwise there is a potential race condition where the throttler's using block is exited before some Tasks have had their "post" action completed, which references the throttler, resulting in an exception due to accessing a disposed object.
            await Task.WhenAll(postTaskTasks.ToArray());
        }
    }

Et ensuite, créez votre liste de tâches et appelez la fonction pour les exécuter, avec un maximum de 4 simultanément à la fois, vous pouvez le faire:

var listOfTasks = new List<Task>();
foreach (var file in files)
{
    var localFile = file;
    // Note that we create the Task here, but do not start it.
    listOfTasks.Add(new Task(async () => await UploadFile(localFile)));
}
await Tasks.StartAndWaitAllThrottledAsync(listOfTasks, 4);

De plus, comme cette méthode prend en charge async, elle ne bloquera pas le thread d'interface utilisateur, contrairement à l'utilisation de Parallel.Invoke ou Parallel.ForEach.

2
deadlydog

J'ai codé ci-dessous la technique où j'utilise BlockingCollection en tant que gestionnaire de comptage de threads. Il est assez simple à implémenter et à gérer le travail . Il accepte simplement les objets Task et ajoute une valeur entière à la liste de blocage, ce qui augmente le nombre de threads en cours de 1. Lorsque le thread se termine, il met en file d'attente l'objet et libère le bloc lors de l'opération add pour les tâches à venir. 

        public class BlockingTaskQueue
        {
            private BlockingCollection<int> threadManager { get; set; } = null;
            public bool IsWorking
            {
                get
                {
                    return threadManager.Count > 0 ? true : false;
                }
            }

            public BlockingTaskQueue(int maxThread)
            {
                threadManager = new BlockingCollection<int>(maxThread);
            }

            public async Task AddTask(Task task)
            {
                Task.Run(() =>
                {
                    Run(task);
                });
            }

            private bool Run(Task task)
            {
                try
                {
                    threadManager.Add(1);
                    task.Start();
                    task.Wait();
                    return true;

                }
                catch (Exception ex)
                {
                    return false;
                }
                finally
                {
                    threadManager.Take();
                }

            }

        }
0
mca