A basic solution that uses two tasks per number: One comprising the computation (executed in the pool), another continuation task performing the GUI update (forced in the context of the main thread):
private void buttonFactorize_Click(object sender, RoutedEventArgs e)
{
foreach (var n in numbers)
{
// mark as running
n.label.Background = Brushes.LightCoral;
var t = new Task(() =>
{
n.result = Factor.factorize(n.number);
});
t.ContinueWith((tx) =>
{
n.label.Content = n.resultAsString();
n.label.Background = n.result.Length > 1 || n.result[0].exponent > 1 ? Brushes.LightGreen : Brushes.LightYellow;
}, TaskScheduler.FromCurrentSynchronizationContext());
t.Start();
}
}
To complete the assignment, we need to disable both buttons at the beginning:
buttonFactorize.IsEnabled = false;
buttonGenerate.IsEnabled = false;
and then re-enable them once all the tasks are completed.
Perhaps the simplest way in this scenario is to count the pending tasks --- i.e., have a counter initialized to numbers.size()
at the beginning and decrement every time a number is computed. If the decrement is placed in the continuation task, no synchronization is required. The continuation task may include the following code:
if (--taskCount <= 0)
{
buttonFactorize.IsEnabled = true;
buttonGenerate.IsEnabled = true;
}
Using async
/await
is technically possible, but it will slightly alter the behavior. Awaiting the tasks in the loop will enforce the linearization of results updates, so the final behavior will be slightly different.