Any idea how to prevent two tasks to update the same progressbar in
the listView? Or how to update the progress bar from tasks running
concurrently?
If you have more than one task running in parallel, and you really want to show the progress of each individual task to the user, you'd need to show a separate progress bar for each task.
How you'd do it depends on the UI structure. E.g., if you have a listview where each item is a task, you can add a progress bar as a child window for each listview item:
This may be an overkill though, usually it's enough to have a single progress bar to track the overall progress. I.e., if you have a 100 tasks, your progress bar will show 100% when all 100 tasks have completed.
Note although, that task #50 (e.g.) may become completed before the task #45, so you can't use the task's number to update the progress. To show the progress correctly, you'd count completed tasks, and use that counter as the progress indicator.
Updated to address the comment:
I have 4 progressbars in the listview and 500 tasks. At any given time
there are only 4 tasks running concurrently due to the limited
scheduler. I try to assign a progress bar in the listview with free
status to a new incoming task and then set the status of the
progressbar to free when the task is done so that the progressbar can
be reused by another new incoming task. Does that make sense? Or am I
going to a deadend?
I'm not sure it is a sensible UI design decision, but if you want it that way, you can use SemaphoreSlim
to allocate a progress bar as a limited resource. Example:
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<Task> _pendingTasks;
Queue<ProgressBar> _availableProgressBars;
// do all Task
async Task DoWorkAsync()
{
_availableProgressBars = new Queue<ProgressBar>();
_pendingTasks = new HashSet<Task>();
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<int> progress =
new Progress<int>(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<T>(Func<T> func)
{
lock (_lock)
return func();
}
// execute action inside a lock
void WithLock(Action action)
{
lock (_lock)
action();
}
}
}
This code doesn't use a custom task scheduler, SemaphoreSlim
is enough to limit the concurrency. Note also that protective locks (WithLock
) are redundant here, because everything besides the Task.Run
lambda runs the on UI thread. However, I decided to keep the locks as your application may have a different threading model. In this case, wherever you access ProgressBar
or other UI, you should use BeginInvoke
or like to do it on the UI thread.
Also, check "Async in 4.5: Enabling Progress and Cancellation in Async APIs" for more details about Progress<T>
pattern.