comment mettre à jour la barre de progression des tâches exécutées simultanément

Je suis essayer de lier une tâche parallèle à une liste qui contient pprogressBars. J’utilise un ordonnanceur limité qui n’autorise que le maximum spécifié de degré de parallèle. Jusqu’à présent, cela fonctionne la plupart du temps, mais occasionnellement, deux tâches mettent à jour la même barre de progression dans listView. Ci-dessous mon code.

Une idée de comment empêcher deux tâches de mettre à jour la même barre de progression dans listView? Ou comment mettre à jour la barre de progression à partir de tâches exécutées simultanément?

public class MyClass { public ObservableCollection StatusItems { get; set; } private Object thisLock = new Object(); public int Process() //handled { StatusItems = new ObservableCollection(); for (int i = 0; i < 4; i++) // initialize progress bar collection { StatusInfo sInfo = new StatusInfo(); sInfo.ThreadID = i; sInfo.Table = "No Table"; sInfo.Percentage = 0; sInfo.Status = AppInfo.AVAILABLE; sInfo.Minimum = 0; sInfo.Maximum = 100; sInfo.Visibility = Visibility.Visible; StatusItems.Add(sInfo); } Parent.StatusItems = StatusItems; // assign to viewmodel int numberOfBackGroundThread = 4; LimitedTaskScheduler scheduler = new LimitedTaskScheduler(numberOfBackGroundThread); TaskFactory factory = new TaskFactory(scheduler); var ui = LimitedTaskScheduler.FromCurrentSynchronizationContext(); Task[] tasks = new Task[rows.Count]; for (int i = 0; i < rows.Count; i++) { ...... tasks[i] = factory.StartNew(() => { int barIndex = -1; int threadID = Thread.CurrentThread.ManagedThreadId; cnt++; if (cnt > numberOfBackGroundThread - 1) { while (true) { for (int j = 0; j = 0) { break; } // break while loop } } else { barIndex = cnt; } StatusItems[barIndex].TabType = tabType; StatusItems[barIndex].ThreadID = threadID; int nStatus = IndividualProcess(barIndex); if (nStatus  { AppInfo.Finished = true; }); done.ContinueWith(completedTasks => { int nStatus = PostProcess(); }, ui); return returnStatus; } private int IndividualProcess(int barIndex) { for (int i=0; i< 100; i++) { perform work... SetProgressbar (i, StatusItems, barIndex, "in progress") } SetProgressbar (100, StatusItems, barIndex, "DONE") } public void SetProgressbar(int pPercent, ObservableCollection pInfo, int pInfoIndex, ssortingng pStatus) { try // Increment percentage for COPY or nested PURGE { if (Application.Current.Dispatcher.Thread != System.Threading.Thread.CurrentThread) { Application.Current.Dispatcher.BeginInvoke(new Action(() => { ((StatusInfo)pInfo[pInfoIndex]).Percentage = pPercent; ((StatusInfo)pInfo[pInfoIndex]).Status = pStatus; ((StatusInfo)pInfo[pInfoIndex]).PCT = pPercent.ToSsortingng() + "%"; })); } else // When the current thread is main UI thread. The label won't be updated until the EntityCopy() finishes. { ((StatusInfo)pInfo[pInfoIndex]).Percentage = pPercent; ((StatusInfo)pInfo[pInfoIndex]).Status = pStatus; ((StatusInfo)pInfo[pInfoIndex]).PCT = pPercent.ToSsortingng() + "%"; } } catch { throw; } } } public class LimitedTaskScheduler : TaskScheduler { // Fields // Whether the current thread is processing work items. [ThreadStatic] private static bool _currentThreadIsProcessingItems; // The list of tasks to be executed. private readonly LinkedList _tasks = new LinkedList(); // protected by lock(_tasks) /// The maximum concurrency level allowed by this scheduler. private readonly int _maxDegreeOfParallelism; /// Whether the scheduler is currently processing work items. private int _delegatesQueuedOrRunning = 0; // protected by lock(_tasks) ///  /// Initializes an instance of the LimitedConcurrencyLevelTaskScheduler class with the /// specified degree of parallelism. ///  /// The maximum degree of parallelism provided by this scheduler. public LimitedTaskScheduler(int maxDegreeOfParallelism) { if (maxDegreeOfParallelism < 1) throw new ArgumentOutOfRangeException("maxDegreeOfParallelism"); _maxDegreeOfParallelism = maxDegreeOfParallelism; } /// Queues a task to the scheduler. /// The task to be queued. protected sealed override void QueueTask(Task task) { // Add the task to the list of tasks to be processed. If there aren't enough // delegates currently queued or running to process tasks, schedule another. lock (_tasks) { _tasks.AddLast(task); if (_delegatesQueuedOrRunning < _maxDegreeOfParallelism) { ++_delegatesQueuedOrRunning; NotifyThreadPoolOfPendingWork(); } } } ///  /// Informs the ThreadPool that there's work to be executed for this scheduler. ///  private void NotifyThreadPoolOfPendingWork() { ThreadPool.UnsafeQueueUserWorkItem(_ => { // Note that the current thread is now processing work items. // This is necessary to enable inlining of tasks into this thread. _currentThreadIsProcessingItems = true; try { // Process all available items in the queue. while (true) { Task item; lock (_tasks) { // When there are no more items to be processed, // note that we're done processing, and get out. if (_tasks.Count == 0) { --_delegatesQueuedOrRunning; break; } // Get the next item from the queue item = _tasks.First.Value; _tasks.RemoveFirst(); } // Execute the task we pulled out of the queue base.TryExecuteTask(item); } } // We're done processing items on the current thread finally { _currentThreadIsProcessingItems = false; } }, null); } /// Attempts to execute the specified task on the current thread. /// The task to be executed. ///  /// Whether the task could be executed on the current thread. protected sealed override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) { // If this thread isn't already processing a task, we don't support inlining if (!_currentThreadIsProcessingItems) return false; // If the task was previously queued, remove it from the queue if (taskWasPreviouslyQueued) TryDequeue(task); // Try to run the task. return base.TryExecuteTask(task); } /// Attempts to remove a previously scheduled task from the scheduler. /// The task to be removed. /// Whether the task could be found and removed. protected sealed override bool TryDequeue(Task task) { lock (_tasks) return _tasks.Remove(task); } /// Gets the maximum concurrency level supported by this scheduler. public sealed override int MaximumConcurrencyLevel { get { return _maxDegreeOfParallelism; } } /// Gets an enumerable of the tasks currently scheduled on this scheduler. /// An enumerable of the tasks currently scheduled. protected sealed override IEnumerable GetScheduledTasks() { bool lockTaken = false; try { Monitor.TryEnter(_tasks, ref lockTaken); if (lockTaken) return _tasks.ToArray(); else throw new NotSupportedException(); } finally { if (lockTaken) Monitor.Exit(_tasks); } } } 

Mise à jour: le planificateur de tâches peut ne pas être pertinent. Je le mets ici juste au cas où quelqu’un pourrait trouver quelque chose de nouveau que je n’aurais jamais pensé à faire ou à manquer.

Toute idée de comment empêcher deux tâches de mettre à jour la même barre de progression dans listView? Ou comment mettre à jour la barre de progression à partir de tâches exécutées simultanément?

Si vous avez plusieurs tâches exécutées en parallèle et que vous souhaitez réellement montrer la progression de chaque tâche à l’utilisateur, vous devez afficher une barre de progression distincte pour chaque tâche.

Cela dépend de la structure de l’interface utilisateur. Par exemple, si vous avez une liste où chaque élément est une tâche, vous pouvez append une barre de progression en tant que fenêtre enfant pour chaque élément de la liste:

Listview avec barre de progression

Cela peut toutefois être exagéré, il suffit généralement d’une barre de progression unique pour suivre la progression globale. Par exemple, si vous avez 100 tâches, votre barre de progression indiquera 100% lorsque toutes les 100 tâches seront terminées.

Notez cependant que la tâche n ° 50 (par exemple) peut être terminée avant la tâche n ° 45, vous ne pouvez donc pas utiliser le numéro de la tâche pour mettre à jour la progression. Pour afficher la progression correctement, vous devez compter les tâches terminées et utiliser ce compteur comme indicateur de progression.

Mis à jour pour répondre au commentaire:

J’ai 4 barres de progression dans la liste et 500 tâches. À tout moment, seules 4 tâches sont exécutées simultanément en raison du nombre limité de planificateurs. J’essaie d’atsortingbuer une nouvelle tâche entrante à une nouvelle tâche entrante dans la vue liste avec vue libre, puis de définir le statut de la barre de progression libre lorsque la tâche est terminée afin que la barre de progression puisse être réutilisée par une nouvelle tâche entrante. Cela a-t-il du sens? Ou est-ce que je vais au bout?

Je ne suis pas sûr que ce soit une décision de conception d’interface utilisateur judicieuse, mais si vous le souhaitez, vous pouvez utiliser SemaphoreSlim pour allouer une barre de progression en tant que ressource limitée. Exemple:

 using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace TaskProgress { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private async void Form1_Load(object sender, EventArgs e) { await DoWorkAsync(); } const int MAX_PARALLEL = 4; readonly object _lock = new Object(); readonly SemaphoreSlim _semaphore = new SemaphoreSlim(initialCount: MAX_PARALLEL); HashSet _pendingTasks; Queue _availableProgressBars; // do all Task async Task DoWorkAsync() { _availableProgressBars = new Queue(); _pendingTasks = new HashSet(); var progressBars = new ProgressBar[] { this.progressBar1, this.progressBar2, this.progressBar3, this.progressBar4 }; foreach (var item in progressBars) _availableProgressBars.Enqueue(item); for (int i = 0; i < 50; i++) // start 50 tasks QueueTaskAsync(DoTaskAsync()); await Task.WhenAll(WithLock(() => _pendingTasks.ToArray())); } // do a sigle Task readonly Random _random = new Random(Environment.TickCount); async Task DoTaskAsync() { await _semaphore.WaitAsync(); try { var progressBar = WithLock(() => _availableProgressBars.Dequeue()); try { progressBar.Maximum = 100; progressBar.Value = 0; IProgress progress = new Progress(value => progressBar.Value = value); await Task.Run(() => { // our simulated work takes no more than 10s var sleepMs = _random.Next(10000) / 100; for (int i = 0; i < 100; i++) { Thread.Sleep(sleepMs); // simulate work item progress.Report(i); } }); } finally { WithLock(() => _availableProgressBars.Enqueue(progressBar)); } } finally { _semaphore.Release(); } } // Add/remove a task to the list of pending tasks async void QueueTaskAsync(Task task) { WithLock(() => _pendingTasks.Add(task)); try { await task; } catch { if (!task.IsCanceled && !task.IsFaulted) throw; return; } WithLock(() => _pendingTasks.Remove(task)); } // execute func inside a lock T WithLock(Func func) { lock (_lock) return func(); } // execute action inside a lock void WithLock(Action action) { lock (_lock) action(); } } } 

Ce code n’utilise pas de planificateur de tâches personnalisé, SemaphoreSlim est suffisant pour limiter la simultanéité. Notez également que les verrous de protection ( WithLock ) sont redondants ici, car tout Task.Run lambda Task.Run exécute le thread sur l’interface utilisateur. Cependant, j’ai décidé de conserver les verrous car votre application pourrait avoir un modèle de thread différent. Dans ce cas, partout où vous accédez à ProgressBar ou à une autre interface utilisateur, vous devez utiliser BeginInvoke ou aimer le faire sur le fil de l’interface utilisateur.

Consultez également “Async dans la version 4.5: Activation de la progression et de l’annulation dans les API asynchrones” pour plus de détails sur Progress modèle de Progress .