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.
await Task.Run()
how do you get a handle to that awaited task? I need to add the task to myList<Task<int>>
. – Mark Richmanawait Task.WhenAll(tasks);
outside my loop. Works!!!! – Mark Richman