3

I have a WPF app which, upon button click, creates a List<Task<int>> and starts these tasks. My assumption is that the Add() call starts these in parallel, but async.

This is my function that does a bunch of WMI calls in serial on a remote machine:

AgentBootstrapper.cs

public async Task<int> BootstrapAsync(BootstrapContext context, IProgress<BootstrapAsyncProgress> progress)
{
  ...

  do a bunch of stuff in serial *without* await calls

  ...

  if (progress != null)
  {
      progress.Report(new BootstrapAsyncProgress
      {
          MachineName = context.MachineName, 
          ProgressPercentage = 30, 
          Text = "Copying install agent software to \\\\" + context.MachineName + "\\" + context.ShareName
      });
  }

  ...

  return pid; // ProcessId of the remote agent that was just started
}

And this is obviously my button handler in the UI:

Shell.xaml.cs

private async void InstallButton_Click(object sender, RoutedEventArgs e)
{
    var bootstrapTasks = new List<Task<int>>();

    var progress = new Progress<BootstrapAsyncProgress>();
    progress.ProgressChanged += (o, asyncProgress) =>
    {
        Debug.WriteLine("{0}: {1}% {2}", asyncProgress.MachineName, asyncProgress.ProgressPercentage,
            asyncProgress.Text);

        //TODO Update ViewModel property for ProgressPercentage
    };

    var vm = DataContext as ShellViewModel;

    Debug.Assert(vm != null);

    foreach (var targetMachine in vm.TargetMachines)
    {
        var bootstrapContext = new BootstrapContext(targetMachine.MachineName, true)
        {
            AdminUser = vm.AdminUser,
            AdminPassword = vm.AdminPassword
        };

        var bootstrapper = new AgentBootstrapper(bootstrapContext);
        bootstrapTasks.Add(bootstrapper.BootstrapAsync(bootstrapContext, progress)); // UI thread locks up here
    }
}

I know functions marked as async should have function calls within them using await. In my case, these are all calls to some synchronous WMi helper functions which all return void. So, I don't think await is what I want here.

Simply put, I want all the bootstrapTasks items (the calls to bootstrapper.BootstrapAsync() to fire at once, and have the UI thread receive progress events from all of them. When the whole lot are complete, I'll need to handle that too.

Update 1

Attempting to use Task.Run() fixes the UI locking issue, but only the first Task instance is executed. Update foreach loop:

foreach (var targetMachine in vm.TargetMachines)
{
    var tm = targetMachine; // copy closure variable
    var bootstrapContext = new BootstrapContext(tm.MachineName, true)
    {
        AdminUser = vm.AdminUser,
        AdminPassword = vm.AdminPassword
    };

    var bootstrapper = new AgentBootstrapper(bootstrapContext);

    Debug.WriteLine("Starting Bootstrap task on default thread pool...");
    var task = Task.Run(() =>
    {
        var pid = bootstrapper.Bootstrap(bootstrapContext, progress);
        return pid;
    });

    Debug.WriteLine("Adding Task<int> " + task.Id + " to List<Task<int>>.");
    tasks.Add(task);

    await Task.WhenAll(tasks);  // Don't proceed with the rest of this function untill all tasks are complete
}

Update 2

Moving the await Task.WhenAll(tasks); outside the foreach loop allows all tasks to run in parallel.

7
  • I had to do something like this. I used the task completion source and polled the list with linq. Progress reporting was done via a synchronization context that accompanied each task. Commented Apr 27, 2014 at 23:40
  • @GarryVass I just updated my question with some more info. Is your approach still applicable? Commented Apr 27, 2014 at 23:46
  • @MarkRichman, FYI: stackoverflow.com/a/21357567/1768303 Commented Apr 27, 2014 at 23:53
  • @Noseratio Ok, but if you await Task.Run() how do you get a handle to that awaited task? I need to add the task to my List<Task<int>>. Commented Apr 28, 2014 at 0:00
  • Stupid me - I just had to move await Task.WhenAll(tasks); outside my loop. Works!!!! Commented Apr 28, 2014 at 0:04

2 Answers 2

5

Nothing in the code generated for async/await involves the creation of threads. Using the async keyword does not cause another thread to be used. All async does is allow you to use the await keyword. If you want something to happen on another thread, try using Task.Run.

Sign up to request clarification or add additional context in comments.

7 Comments

I don't particularly care if it's on a different thread or not. I just can't have the main thread lock up.
@MarkRichman My point is if you make a blocking call it is blocking on the UI thread because you never moved anything off that thread. You need to put all that serial blocking code into an action and invoke it with Task.Run.
Thanks I think that did it. I replaced the Add() call with this line await Task.Run(() => bootstrapper.BootstrapAsync(bootstrapContext, progress));. The UI isn't locking up. So, does BootstrapAsync even need to be an async method at this point? Also, how will those progress events find their way back up to their event handler which lives on the UI thread?
@MarkRichman It would not need to be async if you never await. The progress events will be raised on the UI thread because the Progress<T> class captures the SynchronizationContext of the thread on which it is created. It is similar to the way BackgroundWorker works with ReportProgress.
It looks like it's still blocking after the first iteration in the foreach loop at int pid = await Task.Run(() => bootstrapper.Bootstrap(bootstrapContext, progress)); I get the progress message back up to the handler, but the other Tasks don't run in parallel.
|
1

Run the tasks on the thread pool (using the default task scheduler, that is) and await Task.WhenAll(bootstrapTasks) on them in your UI thread?

4 Comments

That doesn't compile because Task.WaitAll() returns void.
That had no effect unfortunately. It never gets to this line. It's locked up in the foreach loop doing the Add().
Don't just call Bootstrap.Async - it will block, instead, run it as a Task.
If you keep editing your answer, these comments will make no sense.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.