Task Parallel

While all this is important, there are cases where this solution lacks enough flexibility, and that's why we include Task Parallel Library in the set of the software tools available.

We've seen the basics of the Task object in Chapter 3, Advanced Concepts of C# and .NET, and Chapter 12, Performance, but now it's time to look at some more advanced aspects that make this object one of the most interesting in .NET Framework regarding parallel programming.

Communication between threads

As you know, the results obtained after task completions can be of any type (Generics included).

When you create a new Task<T> object, you inherit several methods and properties to facilitate data manipulation and retrieval. For example, you have properties such as Id, IsCancelled, IsCompleted, IsFaulted, and Status to determine the state of the task and a Result property, which contains the returning value of the task.

As for the methods available, you have a Wait method to force the Task object to wait until completion, and another very useful method called ContinueWith. With this method, you can code what to do when the task is finished, knowing that the results are obtainable from the Result property.

So, let's imagine a situation like we did in the earlier demo about reading and manipulating files in a directory – only this time, we're just reading the names and using a Task object.

With all this functionality, we might think that the following code should work correctly:

private void btnRead_Click(object sender, EventArgs e)
{
var getFiles = newTask<List<string>>(() =>  getListOfIconsAsync());
  getFiles.Start();
  getFiles.ContinueWith((f) => UpdateUI(getFiles.Result));
}
private List<string> getListOfIconsAsync()
{
  string[] files = Directory.GetFiles(filesPath, "*.png");
  return files.ToList();
}
private void UpdateUI(List<string> filenames)
{
  listBox1.Items.Clear();
  listBox1.DataSource = filenames;
}

As you can see, we create a new Task<List<string>> object instance; so, we can take advantage of its functionality and invoke ContinueWith to update the user interface with the results.

However, we get InvalidOperationException in the UpdateUI method because it's still the Task (another thread) that is trying to access a different thread. And it does not matter that the results have been obtained correctly, as you can see in this screenshot, showing the value of Result:

Communication between threads

Fortunately, we have a solution linked to the TaskScheduler object, which is part of this set of tools. We just have to pass another argument to the ContinueWith method, indicating the FromCurrentSynchronizationContext property.

So, we'll modify the previous call as follows:

getFiles.ContinueWith((f) => UpdateUI(getFiles.Result),
TaskScheduler.FromCurrentSynchronizationContext());

Now everything works perfectly, as you can see in the final screenshot of the execution:

Communication between threads

And there it is! A very simple form of updating the user interface from a task without needing complex constructions or other specific objects.

Also, note that this method has up to 40 overloads in order to allow us the behavior configuration in many different ways:

Communication between threads

Other interesting possibilities linked to the Task object are related to some of its static methods, especially WaitAll, WaitAny, WhenAll, and WhenAny. Let's look at what they do:

  • WaitAll: Waits for all the provided Task objects to complete the execution (it receives a collection of the Task objects)
  • WaitAny: It has the same structure as WaitAll, but it waits for the first task to finish
  • WhenAll: Creates a new task that executes only when all the provided tasks are completed
  • WhenAny: The same structure as the earlier one, but it waits for the first task to finish

And there's still another interesting feature: ContinueWhenAll, which guarantees that something is done only when all tasks passed as arguments are finished.

Let's take an example to see how this works. We have three algorithms for image manipulation: the three receive a Bitmap object and return another bitmap, transformed. You can read the algorithms in the demo code (they are named BitmapInvertColors, MakeGrayscale, and CorrectGamma).

When the button is clicked on, four tasks are created: each one calling to a method in charge of transforming a bitmap and presenting the result in a different pictureBox control. And we use the previous ContinueWith method to update a label's text in the user interface so that we know the order in which they execute.

The code is as follows:

private void btnProcessImages_Click(object sender, EventArgs e)
{
  lblMessage.Text = "Tasks finished:";
  var t1 = Task.Factory.StartNew(() => pictureBox1.Image =
    Properties.Resources.Hockney_2FIGURES);
  t1.ContinueWith((t) => lblMessage.Text += " t1-",
    TaskScheduler.FromCurrentSynchronizationContext());
  var t2 = Task.Factory.StartNew(() => pictureBox2.Image =
    BitmapInvertColors(Properties.Resources.Hockney_2FIGURES));
  t2.ContinueWith((t) => lblMessage.Text += " t2-",
    TaskScheduler.FromCurrentSynchronizationContext());
  var t3 = Task.Factory.StartNew(() => pictureBox3.Image =
    MakeGrayscale(Properties.Resources.Hockney_2FIGURES));
  t3.ContinueWith((t) => lblMessage.Text += " t3-",
    TaskScheduler.FromCurrentSynchronizationContext());
  var t4 = Task.Factory.StartNew(() => pictureBox4.Image =
    CorrectGamma(Properties.Resources.Hockney_2FIGURES, 2.5m));
  //var t6 = Task.Factory.StartNew(() => Loop());
  t4.ContinueWith((t) => lblMessage.Text += " t4-",
    TaskScheduler.FromCurrentSynchronizationContext());
  var t5 = Task.Factory.ContinueWhenAll(new[] { t1, t2, t3, t4 }, (t) =>
  {
    Thread.Sleep(50);
  });
  t5.ContinueWith((t) => lblMessage.Text += " –All finished",
    TaskScheduler.FromCurrentSynchronizationContext());
}

If we want the All finished label to update the last one, we need a way to make sure that the fifth Task is executed as the latest in the sequence (of course, if we don't use a Task, it would be updated as the first).

As you can see in the next screenshot, the order of the second, third, and fourth tasks will be random, but the first one (because it doesn't do any heavy work; it only loads the original image) will always appear heading the sequence and the fifth one will appear the latest:

Communication between threads

There are other interesting features still, similar to the ones we saw earlier in the parallel demos in relation to cancellation.

To cancel a task, we will use a similar procedure—only in this case, it is simpler. I'll use a Console application to show it in a couple of simple methods:

static void Main(string[] args)
{
  Console.BackgroundColor = ConsoleColor.Gray;
  Console.WindowWidth = 39;
  Console.WriteLine("Operation started...");
  var cs = newCancellationTokenSource();
  var t = Task.Factory.StartNew(
    () => DoALongWork(cs)
  );
  Thread.Sleep(500);
  cs.Cancel();
  Console.Read();
}
private static void DoALongWork(CancellationTokenSource cs)
{
  try
  {
    for (int i = 0; i < 100; i++)
    {
      Thread.Sleep(10);
      cs.Token.ThrowIfCancellationRequested();
    }
  }
  catch (OperationCanceledException ex)
  {
    Console.WriteLine("Operation Cancelled. 
 Cancellation requested: " + ex.CancellationToken.IsCancellationRequested);
  }
}

As you can see, we generate a Task over a DoALongWork method, which includes a delay of a tenth of a second in a 100-iteration loop. However, in every iteration, we check the value of the ThrowIfCancellationRequested method, which belongs to the CancellationTokenSource method previously generated at task creation, and passes it to the slow method.

After 500 milliseconds, cs.Cancel() is called in the main thread, thread execution stops, and Exception is launched and recovered on the catch side in order to present the output in the Console as a message, showing whether the cancellation was really requested.

The next screenshot shows what you should see when executing this code:

Communication between threads

Up until here, this was a review of Task Parallel Library and some of its most interesting possibilities.

We'll move toward the end of this book by talking about the latest innovations in .NET now: the so-called NET Core 1.0, which is intended to execute on all platforms, including Linux and MacOS.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.37.154