Concurrently awaiting multiple asynchronous calls with independent continuations - c#

There are several scenarios where I need to invoke multiple asynchronous calls (from the same event handler) that can proceed independently of each other, with each one having its own continuation for updating the UI.
The following naive implementation causes the three asynchronous operations to be executed sequentially:
private async void button_Click(object sender, RoutedEventArgs e)
{
nameTextBox.Text = await GetNameAsync();
cityTextBox.Text = await GetCityAsync();
rankTextBox.Text = await GetRankAsync();
}
There's an MSDN example that suggests separating the creation of the tasks from their respective await statements, allowing them to be run in parallel:
private async void button_Click(object sender, RoutedEventArgs e)
{
var nameTask = GetNameAsync();
var cityTask = GetCityAsync();
var rankTask = GetRankAsync();
nameTextBox.Text = await nameTask;
cityTextBox.Text = await cityTask;
rankTextBox.Text = await rankTask;
}
However, the limitation of this approach is that the task continuations are still registered sequentially, meaning that the nth continuation can't execute before all its preceding n−1 continuations have completed, even though its task may be the first to complete.
What is the best pattern for getting the asynchronous tasks to run in parallel, but have each continuation run as soon as its respective task completes?
Edit: Most of the answers suggest awaiting on Task.WhenAll, like below:
private async void button_Click(object sender, RoutedEventArgs e)
{
var nameTask = GetNameAsync();
var cityTask = GetCityAsync();
var rankTask = GetRankAsync();
await Task.WhenAll(nameTask, cityTask, rankTask);
nameTextBox.Text = nameTask.Result;
cityTextBox.Text = cityTask.Result;
rankTextBox.Text = rankTask.Result;
}
However, this does not meet my requirement, as I need each continuation to execute as soon as its respective task completes. For example, suppose that GetNameAsync takes 5 s, GetCityAsync takes 2 s, and GetRankAsync takes 8 s. The implementation above would cause all three textboxes to only be updated after 8 s, even though the results for nameTextBox and cityTextBox were known much earlier.

The traditional approach was to use ContinueWith for registering the respective continuation to each asynchronous task:
private async void button_Click(object sender, RoutedEventArgs e)
{
TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
await Task.WhenAll(
GetNameAsync().ContinueWith(nameTask => { nameTextBox.Text = nameTask.Result; }, uiScheduler),
GetCityAsync().ContinueWith(cityTask => { cityTextBox.Text = cityTask.Result; }, uiScheduler),
GetRankAsync().ContinueWith(rankTask => { rankTextBox.Text = rankTask.Result; }, uiScheduler));
}
With C# 5, it is now preferable to use an await-style pattern. This may be most easily achieved by splitting up each task–continuation pair into its own method:
private async void button_Click(object sender, RoutedEventArgs e)
{
await Task.WhenAll(
PopulateNameAsync(),
PopulateCityAsync(),
PopulateRankAsync());
}
private async Task PopulateNameAsync()
{
nameTextBox.Text = await GetNameAsync();
}
private async Task PopulateCityAsync()
{
cityTextBox.Text = await GetCityAsync();
}
private async Task PopulateRankAsync()
{
rankTextBox.Text = await GetRankAsync();
}
Defining all these trivial methods quickly become cumbersome, so one may condense them into async lambdas instead:
private async void button_Click(object sender, RoutedEventArgs e)
{
await Task.WhenAll(
new Func<Task>(async () => { nameTextBox.Text = await GetNameAsync(); })(),
new Func<Task>(async () => { cityTextBox.Text = await GetCityAsync(); })(),
new Func<Task>(async () => { rankTextBox.Text = await GetRankAsync(); })());
}
If this pattern is used frequently, it would also be helpful to define a utility method that can take the Func<Task> lambdas and execute them, making our event handler's code more concise and readable:
public static Task WhenAllTasks(params Func<Task>[] taskProducers)
{
return Task.WhenAll(taskProducers.Select(taskProducer => taskProducer()));
}
private async void button_Click(object sender, RoutedEventArgs e)
{
await WhenAllTasks(
async () => nameTextBox.Text = await GetNameAsync(),
async () => cityTextBox.Text = await GetCityAsync(),
async () => rankTextBox.Text = await GetRankAsync());
}

A simpler alternative would be:
private async void button_Click(object sender, RoutedEventArgs e)
{
var results = await Task.WhenAll(
GetNameAsync(),
GetCityAsync(),
GetRankAsync()
);
nameTextBox.Text = results[0];
nameCityBox.Text = results[1];
nameRankBox.Text = results[2];
}
No closures, no extra state machines.

As I understand it, you need to query three async resources and only update the UI once all three have returned. If that's correct the Task.WhenAll control is idea to use. Something like
private async void button_Click(object sender, RoutedEventArgs e)
{
string nametext = null;
string citytext = null;
string ranktext = null;
await Task.WhenAll(
async () => nametext = await GetNameAsync(),
async () => citytext = await GetCityAsync(),
async () => ranktext = await GetRankAsync()
);
nameTextBox.Text = nametext;
nameCityBox.Text = citytext;
nameRankBox.Text = ranktext;
}

private async void button_Click(object sender, RoutedEventArgs)
{
var nameTask = GetNameAsync();
var cityTask= GetCityAsync();
var rankTask= GetRankAsync();
System.Threading.Tasks.Task.WaitAll(nameTask, cityTask, rankTask);
nameTextBox.Text = nameTask.Result;
cityTextBox.Text = cityTask.Result;
rankTextBox.Text = rankTask.Result;
}
More details: https://msdn.microsoft.com/pt-br/library/dd270695(v=vs.110).aspx

Related

Run and Wait for async Task to complete in a Windows Form Application

How can I wait for an async task to complete without freezing the whole Application?
This function works but Cout() gets called while the File is still downloading.
private void Btn_test_Click(object sender, EventArgs e)
{
var task = Task.Run(async () => { await DownloadWebFile("https://speed.hetzner.de/100MB.bin", AppDomain.CurrentDomain.BaseDirectory + "//100MB.bin"); });
Cout(DownloadSuccessMsg);
}
when I do this the whole Application freezes:
private void Btn_test_Click(object sender, EventArgs e)
{
var task = Task.Run(async () => { await DownloadWebFile("https://speed.hetzner.de/100MB.bin", AppDomain.CurrentDomain.BaseDirectory + "//100MB.bin"); });
task.Wait();
Cout(DownloadSuccessMsg);
}
How can I wait correctly before running other code depending on the downloaded file?
private static async Task DownloadWebFile(string url, string fullPath)
{
using var client = new DownloadManager(url, fullPath);
client.ProgressChanged += (totalFileSize, totalBytesDownloaded, progressPercentage) =>
{
SetProgressBarValue((int)progressPercentage);
};
await client.StartDownload();
}
You can mark the method as async void. Returning void from an asynchronous method is usually not a great idea, but in the case of an event handler it's usually considered acceptable.
private async void Btn_test_Click(object sender, EventArgs e)
{
await DownloadWebFile("https://speed.hetzner.de/100MB.bin", AppDomain.CurrentDomain.BaseDirectory + "//100MB.bin");
Cout(DownloadSuccessMsg);
}

How to use async task as a method with parameters

I get confused, when tried to use async task. Everything is fine if I use this method without parameters, but when I add parameters to it, it says that cannot convert System.Threading.Tasks.Task to System.Func. So how to use it properly and fix this error?
Main:
Timers t = new Timers();
public Form1()
{
InitializeComponent();
t.StartTimerTemplate(TEMPLATEupdateSong("test", "test", "test"));//error occurs in this line
}
async Task TEMPLATEupdateSong(string url, string sender, string labelName)
{
string nowPlaying = await getter.getString(url);
nowPlaying = XDocument.Parse(nowPlaying).ToString();
var outputLabel = this.Controls.OfType<Label>()
.FirstOrDefault(control => control.Name == labelName);
outputLabel.Invoke(new Action(() =>
outputLabel.Text = sender + "\n" + nowPlaying
));
}
Timers.cs:
public void StartTimerTemplate(Func<Task> updateTemplate)
{
System.Timers.Timer timer = new System.Timers.Timer(50000);
timer.AutoReset = true;
timer.Elapsed += (sender, e) => timer_Elapsed(sender, e, updateTemplate());
timer.Start();
}
async void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e, Task task)
{
await task;
}
Your method expects a Func<Task>, not a Task.
t.StartTimerTemplate(() => TEMPLATEupdateSong("test", "test", "test"));
Or
t.StartTimerTemplate(async () => await TEMPLATEupdateSong("test", "test", "test"));

Converting threaded to multi threaded code? ( task factory ?)

I have the following threaded code ( i think ) form with a start and cancel button and a multilined text box, the commented sections (//) are from the working single threaded version and below i have tried to retrofit the multithreaded task.factory parts, however it seems to launch the powershell commands fine ( show in task manager ) but the program completes without waiting for the results from each "heavyOperation".
The idea is to start all the four HeavyOperation tasks at the same time (ish) and wait for each to return the results and append the results to the text box
public partial class Form1 : Form
{
Progress<string> progressReporter = new Progress<string>();
CancellationTokenSource cancelSource;
public Form1()
{
InitializeComponent();
progressReporter.ProgressChanged += progressManager_ProgressChanged;
}
async private void btnStart_Click(object sender, EventArgs e)
{
btnStart.Enabled = false;
btnCancel.Enabled = true;
cancelSource = new CancellationTokenSource();
//textBox1.Text = await Task.Run(() => PerfromTaskAction(cancelSource.Token));
await Task.Run(() => PerfromTaskAction(cancelSource.Token));
lblStatus.Text = "Completed."; btnStart.Enabled = true;
btnCancel.Enabled = false;
}
//private string PerfromTaskAction(CancellationToken ct)
static void PerfromTaskAction(CancellationToken ct)
{
//StringBuilder sb = new StringBuilder();
object[] arrObjects = new object[] { "SERVER1", "SERVER2", "SERVER3", "SERVER4" };
foreach(object i in arrObjects)
{
//if (ct.IsCancellationRequested) break;
//sb.Append(string.Format("{0}: {1}\r\n", HeavyOperation(i.ToString()),i));
//((IProgress<string>)progressReporter).Report(string.Format("Now Checking: {0}...", i));
Task.Factory.StartNew(() => HeavyOperation(i.ToString()));
}
//return sb.ToString();
}
void progressManager_ProgressChanged(object sender, string e)
{
lblStatus.Invoke((Action)(() => lblStatus.Text = e));
}
//private string HeavyOperation(string i)
public static void HeavyOperation(string i)
{
PowerShell ps = PowerShell.Create();
ps.AddCommand("invoke-command");
ps.AddParameter("computername", i);
ps.AddParameter("scriptblock", ScriptBlock.Create("get-vmreplication | select State"));
Collection<PSObject> result = ps.Invoke();
//return result[0].Properties["State"].Value.ToString();
Console.Write(result[0].Properties["State"].Value.ToString());
}
private void btnCancel_Click(object sender, EventArgs e)
{
cancelSource.Cancel();
}
}
Thanks for looking
You also need to await for the HeavyOperations to complete.
You can use Task.WhenAll for this purpose. Here is an async version of PerformTaskAction, using Task.WhenAll
I've taken into account Scott Chamberlain's suggestions:
Changed the unsafe (in async-await context) Task.Factory.StartNew() to Task.Run()
Removed the unnecessary await in the invocation of PerformTaskAction()
Passed the missing CancellationToken in the outer Task.Run() call
static async Task PerfromTaskAction(CancellationToken ct) {
//StringBuilder sb = new StringBuilder();
object[] arrObjects = new object[] { "SERVER1", "SERVER2", "SERVER3", "SERVER4" };
IList<Task> tasks = new List<Task>(); // collect all tasks in single collection
foreach( object i in arrObjects ) {
//if (ct.IsCancellationRequested) break;
//sb.Append(string.Format("{0}: {1}\r\n", HeavyOperation(i.ToString()),i));
//((IProgress<string>)progressReporter).Report(string.Format("Now Checking: {0}...", i));
tasks.Add(Task.Run(() => HeavyOperation(i.ToString())));
}
await Task.WhenAll(tasks).ConfigureAwait(false); // wait asynchronously for all tasks to complete
}
Now that PerformTaskAction is async, you also need to await on it.
Finally you invoke PerformTaskAction, making sure you also pass the CancellationToken.
await Task.Run( ()=> PerformTaskAction(cancelSource.Token), cancelSource.Token);

Is there a difference with Task.Run a void method and Task method returning null?

Consider a form with 2 buttons and a richtextbox:
public partial class MainForm : Form
{
CancellationTokenSource cts;
CancellationToken token;
public MainForm()
{
InitializeComponent();
}
private void MainForm_Load(object sender, EventArgs e)
{
cts = new CancellationTokenSource();
token = cts.Token;
var task = Task.Run(() => WriteSomeLines(), token);
}
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
cts.Dispose();
}
private void btnStart_Click(object sender, EventArgs e)
{
cts = new CancellationTokenSource();
token = cts.Token;
var task = Task.Run(() => WriteSomeLines(), token);
}
private void btnCancel_Click(object sender, EventArgs e)
{
try
{
cts.Cancel();
cts.Dispose();
}
catch (ObjectDisposedException exc)
{
MessageBox.Show(exc.GetType().Name);
//object disposed
}
}
public void WriteSomeLines()
{
if (ControlInvokeRequired(rtbLoops, () => rtbLoops.Text += "Starting new loop \r\n")) ;
else rtbLoops.Text += "Starting new loop \r\n";
for (int i = 0; i < 30; i++)
{
try
{
if (ControlInvokeRequired(rtbLoops, () => { rtbLoops.AppendText("New line " + i + "\r\n"); rtbLoops.ScrollToCaret(); })) ;
else rtbLoops.AppendText("New line " + i + "\r\n");
Thread.Sleep(250);
token.ThrowIfCancellationRequested();
}
catch (OperationCanceledException ae)
{
MessageBox.Show(ae.GetType().Name);
return;
}
}
return;
}
public bool ControlInvokeRequired(Control c, Action a)
{
if (c.InvokeRequired)
c.Invoke(new MethodInvoker(delegate { a(); }));
else
return false;
return true;
}
}
Is there a difference if WriteSomeLines() is returning void and I use return inside, or if WriteSomeLines() returns Task and I do return null there? I read that I cannot use await with void returning methods, but inserting
await task;
after task declaration (in the code above) compiles perfectly fine, and runs with no issues.
Edit:
private async void btnStart_Click(object sender, EventArgs e)
{
cts = new CancellationTokenSource();
token = cts.Token;
var task = Task.Run(() => WriteSomeLines(), token);
await task;
rtbLoops.Text += "Task complete";
}
This compiles with no issues if WriteSomeLines() returns void.
Also, slightly unrealted, am I disposing CancellationTokenSource correctly here?
Second Edit:
So is this the correct approach:
private async void btnStart_Click(object sender, EventArgs e)
{
cts.Dispose();
cts = new CancellationTokenSource();
token = cts.Token;
var task = Task.Run(() => WriteSomeLines(), token);
bool result = await task;
if(result == true) rtbLoops.Text += "Task complete \r\n";
}
and
public async Task<bool> WriteSomeLines()
{
if (ControlInvokeRequired(rtbLoops, () => rtbLoops.Text += "Starting new loop \r\n")) ;
else rtbLoops.Text += "Starting new loop \r\n";
for (int i = 0; i < 30; i++)
{
try
{
if (ControlInvokeRequired(rtbLoops, () => { rtbLoops.AppendText("New line " + i + "\r\n"); rtbLoops.ScrollToCaret(); })) ;
else rtbLoops.AppendText("New line " + i + "\r\n");
await Task.Delay(250);
token.ThrowIfCancellationRequested();
}
catch (OperationCanceledException ae)
{
MessageBox.Show(ae.GetType().Name);
return false;
}
}
return true;
You should never return a null task; that should cause a runtime NullReferenceException error.
You can use await within an async void method, but you cannot use await to consume an async void method (because you cannot await void).
I recommend that you review my async intro blog post; it should help you get a better understanding of async and await.
am I disposing CancellationTokenSource correctly here?
Your start button needs to cancel/dispose the old cts when it creates a new one.

How to chain a task to a previous instance of it?

Looking to chain a task to a previous instance if it exists. Currently, both are executed at the same time.
Initial code that works for one task :
private async void MenuMediaAddFiles_OnClick(object sender, RoutedEventArgs e)
{
var dialog = GetDefaultOpenFileDialog();
using (dialog)
{
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
using (var progress = new SimpleProgress(this))
{
int addFiles = await _context.AddFiles(dialog.FileNames, progress);
Console.WriteLine("Files added: {0}", addFiles);
}
}
}
}
A failed attempt to make it work :
Task<int> _files;
private async void MenuMediaAddFiles_OnClick(object sender, RoutedEventArgs e)
{
var dialog = GetDefaultOpenFileDialog();
using (dialog)
{
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
using (var progress = new SimpleProgress(this))
{
int addFiles;
Task<int> files = _context.AddFiles(dialog.FileNames, progress);
if (_files == null)
{
_files = files;
}
else
{
var task1 = await _files.ContinueWith(task => _context.AddFiles(dialog.FileNames, new SimpleProgress(this)));
}
addFiles = await _files;
Console.WriteLine("Files added: {0}", addFiles);
}
}
}
}
You were pretty close, but there were a few things that needed to be modified:
private Task<int> previousTask = Task.FromResult(0);
private async void MenuMediaAddFiles_OnClick(object sender, RoutedEventArgs e)
{
var dialog = GetDefaultOpenFileDialog();
using (dialog)
{
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
using (var progress = new SimpleProgress(this))
{
previousTask = previousTask.ContinueWith(t =>
_context.AddFiles(dialog.FileNames, progress))
.UnWrap(); ;
int addFiles = await previousTask;
Console.WriteLine("Files added: {0}", addFiles);
}
}
}
}
Things to note:
Rather than having the previous task be null sometimes, it was easier to initialize it to an already completed task (Task.FromResult(0)). This avoids the null check code.
You were calling AddFiles twice. You shouldn't have been calling it before the if, and you weren't ever assigning the task to the instance field inside the if.
I used UnWrap instead of await to turn the Task<Task<int>> into a Task<int>. Both work, but in this case I felt UnWrap made its intentions clearer.
Note that since the entire event handler will be running in the UI thread there's no need to synchronize access to previousTask, if it doesn't, you'd need to do some locking.

Categories

Resources