3
votes

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.

2
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.Gayot Fow
@GarryVass I just updated my question with some more info. Is your approach still applicable?Mark Richman
@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>>.Mark Richman
Stupid me - I just had to move await Task.WhenAll(tasks); outside my loop. Works!!!!Mark Richman

2 Answers

5
votes

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.

1
votes

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?