6

In the following code I disable button before processing tasks and would like to enable it after all tasks are finished.

List<Task> tasks = new List<Task>();
buttonUpdateImage.Enabled = false; // disable button
foreach (OLVListItem item in cellsListView.CheckedItems)
{
    Cell c = (Cell)(item.RowObject);

    var task = Task.Factory.StartNew(() =>
    {
        Process p = new Process();
        ...
        p.Start();
        p.WaitForExit();
    });
    task.ContinueWith(t => c.Status = 0);
    tasks.Add(task);
}

Task.WaitAll(tasks.ToArray());
// enable button here

WaitAll is blocking the UI thread. How can I wait until all tasks finish and then enable the button?

AVee
  • 3,348
  • 17
  • 17
Pablo
  • 28,133
  • 34
  • 125
  • 215

3 Answers3

5

First, I'd install Microsoft.Bcl.Async which will enable the use of async-await in .NET 4.0.

Now, using the answer to this question, you can asynchronously register for process exit, with no need to use Task.Factory.StartNew:

public static class ProcessExtensions
{
    public static Task RunProcessAsync(this Process process, string fileName)
    {
        if (process == null)
            throw new ArgumentNullException(nameof(process));

        var tcs = new TaskCompletionSource<bool>();
        process.StartInfo = new ProcessStartInfo
        {
            FileName = fileName 
        };

        process.EnableRaisingEvents = true
        process.Exited += (sender, args) =>
        {
            tcs.SetResult(true);
            process.Dispose();
        };

        process.Start();
        return tcs.Task;
    }
}

Now, you can do this:

buttonUpdateImage.Enabled = false; // disable button

var tasks = cellsListView.CheckedItems.Cast<OLVListItem>()
                                      .Select(async item => 
{
    Cell cell = (Cell)item.RowObject;

    var process = new Process();
    await process.RunProcessAsync("path");

    cell.Status = 0;
});

await Task.WhenAll(tasks);
buttonUpdateImage.Enabled = true;
Community
  • 1
  • 1
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • Sorry, what is `ProcessUtils`? Googling leads to ambiguous results. I understand RunProcessAsync is extension to `ProcessUtils`? – Pablo Sep 11 '15 at 08:59
  • 1
    @Pablo Edited my code to make it clear. I've made it an extensions method instead of the original utility method. – Yuval Itzchakov Sep 11 '15 at 11:09
4
List<Task> tasks = new List<Task>();
buttonUpdateImage.Enabled = false; // disable button
foreach (OLVListItem item in cellsListView.CheckedItems)
{
    Cell c = (Cell)(item.RowObject);

    var task = Task.Factory.StartNew(() =>
    {
        Process p = new Process();
        ...
        p.Start();
        p.WaitForExit();
    }).ContinueWith(t => c.Status = 0); // this modification is very important, Please note it, this will fix a problem

    tasks.Add(task);
}

// your code has been modified to just make the task object 
// is the continued task instead of the task is the original task 
// Task.WaitAll and add this instead of it.
Task.Factory.ContinueWhenAll(tasks.ToArray(), ac => buttonUpdateImage.Enabled = true;);    

Another way .....

// Note this object.
Barrier b = new Barrier(cellsListView.CheckedItems.Count, () => {
   // enable the button here
   buttonUpdateImage.Enabled = true;
});

buttonUpdateImage.Enabled = false; // disable button
foreach (OLVListItem item in cellsListView.CheckedItems)
{
    Cell c = (Cell)(item.RowObject);

    var task = Task.Factory.StartNew(() =>
    {
          Process p = new Process();
            ...
          p.Start();
          p.WaitForExit();             
     }).ContinueWith(t => {
          c.Status = 0;
          b.SignalAndWait();
     });
}

the first way is better than the second, I advise you to use the first one.

Hakan Fıstık
  • 16,800
  • 14
  • 110
  • 131
3

You can create a new task to run WaitAll method,just like

Task.Factory.StartNew(() =>
{
    Task.WaitAll(tasks.ToArray());
    Action act = () =>
    {
        this.button1.Enabled = true;
    };
    this.button1.Invoke(act);
});
Sky Fang
  • 1,101
  • 6
  • 6
  • is it good idea to have blocking operation and UI access operation together in one task? If it's running in Threadpool then accessing UI may crash it. If it's running in UI thread, then it will be blocked. Unless I miss something... – Pablo Sep 11 '15 at 08:04
  • @Pablo yes,they are work on different thread,so we need to use `Invoke` method – Sky Fang Sep 11 '15 at 08:48