简体   繁体   中英

WPF async await Task Locks UI Thread Running Tasks in Parallel

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.

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 .

在线程池中运行任务(使用默认任务调度程序,即)并在UI线程中await Task.WhenAll(bootstrapTasks)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM