come aggiornare la barra di avanzamento dalle attività in esecuzione contemporaneamente

Sto provando a bind l’attività parallela ad un listView che contiene pprogressBars. Sto usando uno schedulatore limitato che consente solo il massimo specificato di grado parallelo. Finora funziona bene la maggior parte del tempo, ma occasionalmente due attività aggiorna la stessa barra di avanzamento in listView. Di seguito è il mio codice.

Qualche idea su come impedire a due attività di aggiornare la stessa barra di avanzamento in listView? O come aggiornare la barra di avanzamento dalle attività in esecuzione contemporaneamente?

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, string 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.ToString() + "%"; })); } 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.ToString() + "%"; } } 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); } } } 

Aggiornamento: l’Utilità di pianificazione potrebbe non essere rilevante. L’ho messo qui nel caso qualcuno potesse trovare qualcosa di nuovo che non avrei mai pensato di fare o perdere.

Qualche idea su come impedire a due attività di aggiornare la stessa barra di avanzamento in listView? O come aggiornare la barra di avanzamento dalle attività in esecuzione contemporaneamente?

Se hai più di una attività in esecuzione in parallelo e desideri veramente mostrare l’avanzamento di ogni singola attività all’utente, devi mostrare una barra di avanzamento separata per ogni attività.

Come lo faresti dipende dalla struttura dell’interfaccia utente. Ad esempio, se si dispone di un listview in cui ogni elemento è un’attività, è ansible aggiungere una barra di avanzamento come finestra secondaria per ciascun elemento listview:

ListView con barra di avanzamento

Tuttavia, potrebbe trattarsi di un eccesso, in genere è sufficiente disporre di un’unica barra di avanzamento per tenere traccia dei progressi complessivi. Ad esempio, se hai 100 compiti, la barra di avanzamento mostrerà il 100% quando tutte le 100 attività sono state completate.

Si noti, tuttavia, che l’attività n. 50 (ad esempio) potrebbe essere completata prima dell’attività 45, quindi non è ansible utilizzare il numero dell’attività per aggiornare l’avanzamento. Per mostrare correttamente il progresso, devi contare le attività completate e utilizzare quel contatore come indicatore di progresso.

Aggiornato per indirizzare il commento:

Ho 4 progressbar in listview e 500 attività. In qualsiasi momento ci sono solo 4 attività in esecuzione contemporaneamente a causa dello scheduler limitato. Cerco di assegnare una barra di avanzamento in listview con lo stato libero a una nuova attività in entrata e quindi di impostare lo stato della barra di progressione libera quando l’attività viene eseguita in modo che la barra di avanzamento possa essere riutilizzata da un’altra nuova attività in entrata. Ha senso? O sto andando in un vicolo cieco?

Non sono sicuro che sia una decisione di progettazione dell’interfaccia utente ragionevole, ma se lo si desidera in questo modo, è ansible utilizzare SemaphoreSlim per allocare una barra di avanzamento come risorsa limitata. Esempio:

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

Questo codice non utilizza uno schedulatore di attività personalizzato, SemaphoreSlim è sufficiente per limitare la concorrenza. Nota anche che i blocchi di protezione ( WithLock ) sono ridondanti qui, perché tutto oltre il Task.Run lambda esegue il thread dell’interfaccia utente. Tuttavia, ho deciso di mantenere i blocchi poiché l’applicazione potrebbe avere un modello di threading diverso. In questo caso, ovunque tu acceda a ProgressBar o ad un’altra interfaccia utente, dovresti usare BeginInvoke o mi piace farlo sul thread dell’interfaccia utente.

Inoltre, seleziona “Async in 4.5: Abilitazione dell’avanzamento e dell’annullamento nelle API asincrone” per ulteriori dettagli sul modello di Progress .