Showing posts with label await. Show all posts
Showing posts with label await. Show all posts

Monday, August 21, 2023

Why Do You Have to Return "Task" Whenever You "await" Something in a Method in C#?

There is something that has always bothered me in C#: Whenever you "await" something in a method, the return value must be wrapped in a Task.

Note: If you prefer video to text, take a look here: YouTube: Why do you have to return a Task when you use await in a C# method?

The Behavior

Let's start with an example:


    public Person GetPerson(int id)
    {
        List<Person> people = GetPeople();
        Person selectedPerson = people.Single(p => p.Id == id);
        return selectedPerson;
    }

    public async Task<Person> GetPersonAsync(int id)
    {
        List<Person> people = await GetPeopleAsync();
        Person selectedPerson = people.Single(p => p.Id == id);
        return selectedPerson;
    }

The first method (GetPerson) does not have any asynchronous code. It calls the "GetPeople" method that returns a List of Person objects. Then it uses a LINQ method (Single) to pick out an individual Person. Lastly it returns that person. As expected, the return type for the "GetPerson" method is "Person".

The second method (GetPersonAsync) does have asynchronous code. It calls the "GetPeopleAsync" method (which returns a Task<List<Person>>) and then awaits the result. This also gives us a List of Person objects. Then it uses the same LINQ method (Single) to get the selected Person. Lastly it returns that person.

But here's where things are a bit odd: even though our return statement ("return selectedPerson") returns a "Person" type, the method itself ("GetPeopleAsync") returns "Task<Person>".

A Leaky Abstraction

I love using "await" in my code. It is so much easier than dealing with the underlying Tasks directly, and it handles the 95% scenario for me (meaning, 95% of the time, it does what I need -- I only need to drop back to using Task directly when I need more flexibility).

I also really like how "await" lets me write asynchronous code very similarly to how I write non-asynchronous code. Exceptions are raised as expected, and I can use standard try/catch/finally blocks. For the most part, I do not have to worry about "Task" at all when using "await".

It is a very good abstraction over Task.

But it does "leak" in this one spot. A leaky abstraction is one where the underlying implementation shows through. And the return type of a method that uses "await" is one spot where the underlying "Task" implementation leaks through.

This isn't necessarily a bad thing. All abstractions leak to a certain extent. But this particular one has bugged me for quite a while. And it can be difficult to grasp for developers who may not have worked with Task directly.

A Better Understanding

Most of the descriptions I've seen have just said "If you use 'await' in your method, the return type is automatically wrapped in a Task." And there's not much more in the way of explanation.

To get a better understanding of why things work this way, let's get rid of the abstraction and look at using "Task" directly for this code, building things up one line at a time.

If you would like more information on how to use Task manually (along with how to use 'await'), you can take a look at the resources available here: I'll Get Back to You: Task, Await, and Asynchronous Methods in C#.

Step 1: The Method Signature

Let's start with the signature that we would like to have:


    public Person GetPersonAsyncWithTask(int id)
    {

    }

The idea is that I can pass the "id" of a person to this method and get a Person instance back. So this would be my ideal signature.

Step 2: Call the Async Method

Next, we'll call the "GetPersonAsync" method, but instead of awaiting it, we'll get the actual Task back. When I don't know the specific type that comes back from a method, I'll use "var result" to capture the value and then let Visual Studio help me. Here's that code:


    public Person GetPersonAsyncWithTask(int id)
    {
        var result = GetPeopleAsync();
    }

If we hover the cursor over "var", Visual Studio tells us the type we can expect:



This tells us that "result" is "Task<TResult>" and that "TResult is List<Person>". This means that "GetPeopleAsync" returns "Task<List<Person>>". So let's update our code to be more explict:


    public Person GetPersonAsyncWithTask(int id)
    {
        Task<List<Person>> peopleTask = GetPeopleAsync();
    }

Now we have our explicit type, along with a better name for the result: peopleTask.

Step 3: Add a Continuation

The next step is to add a continuation to the Task. By adding a continuation, we are telling our code that after the Task is complete, we would like to "continue" by doing something else.

This is done with the "ContinueWith" method on the task:


    public Person GetPersonAsyncWithTask(int id)
    {
        Task<List<Person>> peopleTask = GetPeopleAsync();
        peopleTask.ContinueWith(task =>
        {

        });        
    }

The "ContinueWith" method takes a delegate (commonly inlined using a lambda expression). In this case, the delegate takes the "peopleTask" as a parameter (which we'll call "task" in the continuation).

For more information on delegates and lambda expressions, you can take a look at the resources available here: Get Func-y: Understanding Delegates in C#.

Step 4: Fill in the Continuation Code

The next step is to fill in the body of our continuation. This is basically "the rest of the method" that we had in our non-asynchronous version:


    public Person GetPersonAsyncWithTask(int id)
    {
        Task<List<Person>> peopleTask = GetPeopleAsync();
        peopleTask.ContinueWith(task =>
        {
            List<Person> people = task.Result;
            Person selectedPerson = people.Single(p => p.Id == id);
            return selectedPerson;
        });        
    }

The first line in the continuation takes the "Result" property of the task (which is the List<Person>) and assigns it to a variable with a friendlier name: "people". Then we use the "Single" method like we did above to get an individual record from the list. Then we return that selected "Person" object.

But Now We Have a Problem

But now we have a problem: we want to return the "selectedPerson" from the "GetPersonAsyncWithTask" method, but it is being returned inside the continuation of the Task instead.

How do we get this out?

It turns out that "ContinueWith" returns a value. Let's use the same technique we used above to figure out what that is.

Step 5: Getting a Return from ContinueWith


    public Person GetPersonAsyncWithTask(int id)
    {
        Task<List<Person>> peopleTask = GetPeopleAsync();
        var result = peopleTask.ContinueWith(task =>
        {
            List<Person> people = task.Result;
            Person selectedPerson = people.Single(p => p.Id == id);
            return selectedPerson;
        });        
    }

Here we have added "var result = " in front of of the call to "peopleTask.ContinueWith". Then if we hover the mouse over "var", we see that this is "Task<TResult>" and "TResult is Person". So this tells us that "ContinueWith" returns a "Task<Person>".

So let's be more explicit with our variable:


    public Person GetPersonAsyncWithTask(int id)
    {
        Task<List<Person>> peopleTask = GetPeopleAsync();
        Task<Person> result = peopleTask.ContinueWith(task =>
        {
            List<Person> people = task.Result;
            Person selectedPerson = people.Single(p => p.Id == id);
            return selectedPerson;
        });        
    }

Now our "result" variable is specifically typed as "Task<Person>".

Step 6: Return the Result

The last step is to return the result:


    public Person GetPersonAsyncWithTask(int id)
    {
        Task<List<Person>> peopleTask = GetPeopleAsync();
        Task<Person> result = peopleTask.ContinueWith(task =>
        {
            List<Person> people = task.Result;
            Person selectedPerson = people.Single(p => p.Id == id);
            return selectedPerson;
        });
        return result;
    }

But we can't stop here. Our return types do not match. The method (GetPersonAsyncWithTask) returns a "Person", and the actual type we return is "Task<Person>".

So we need to update our method signature:


    public Task<Person> GetPersonAsyncWithTask(int id)
    {
        Task<List<Person>> peopleTask = GetPeopleAsync();
        Task<Person> result = peopleTask.ContinueWith(task =>
        {
            List<Person> people = task.Result;
            Person selectedPerson = people.Single(p => p.Id == id);
            return selectedPerson;
        });
        return result;
    }

Now the method returns a "Task<Person>". And this is what we want. If someone awaits what comes back from this method, they will get a "Person" object back. In addition, someone could take this task and set up their own continuation. This is just the nature of Task and how we set up asynchronous code using Task.

Back to the Comparison

So let's go back to our method comparison. But instead of comparing a method with "await" to a non-asynchronous method, let's compare a method with "await" to a method that handles the asynchronous Task manually.


    public async Task<Person> GetPersonAsync(int id)
    {
        List<Person> people = await GetPeopleAsync();
        Person selectedPerson = people.Single(p => p.Id == id);
        return selectedPerson;
    }

    public Task<Person> GetPersonAsyncWithTask(int id)
    {
        Task<List<Person>> peopleTask = GetPeopleAsync();
        Task<Person> result = peopleTask.ContinueWith(task =>
        {
            List<Person> people = task.Result;
            Person selectedPerson = people.Single(p => p.Id == id);
            return selectedPerson;
        });
        return result;
    }

Here's how we can think of this code now (and the compiler magic that happens behind it): anything after the "await" is automatically put into a continuation. What the compiler does is much more complicated, but this is how we can think of it from the programmer's perspective.

If we think about it this way, it's easier to see why the method returns "Task<Person>" instead of just "Person".

"await" is Pretty Magical

When we use "await" in our code, magical things happen behind the scenes. (And I'm saying that as a good thing.) The compiler gets to figure out all of the details of waiting for a Task to complete and what to do next. When I have this type of scenario (the 95% scenario), then it is pretty amazing. It saves me a lot of hassle and let's me work with code that looks more "normal".

The abstraction that "await" gives us is not perfect, though. It does "leak" a little bit. Whenever we "await" something in a method, the return value gets automatically wrapped in a Task. This means that we do need to change the return type of the method to Task. But it is a small price to pay for the ease of using "await".

Wrap Up

I've always had a bit of trouble with having to return a Task from any method that "awaits" something. I mean, I knew that I had to do it, but there was always a bit of cognitive dissonance with saying "the code in this method returns 'Person', but the method itself returns 'Task<Person>'."

By working through this manually, my brain is a little more comfortable. I have a better understanding of what is happening behind the "await", and that if we were to do everything manually, we would end up returning a Task. So it makes sense that after we add the magic of "await", the method will still need to return a Task.

I hope that this has helped you understand things a bit as well. If you have any questions or other ways of looking at this, feel free to leave a comment.

Happy Coding!

Friday, July 9, 2021

A Collection of 2020 Recorded Presentations

2020 was "interesting". One good thing that came out of it is that I had the chance to speak remotely for some user groups and conferences that I would not normally get to attend in person. Many of those presentations were recorded. Here's a list for anyone who is interested

Abstract Art: Getting Abstraction "Just Right" (Apr 2020)

Modern Devs Charlotte

Abstraction is awesome. And abstraction is awful. Too little, and our applications are difficult to extend and maintain. Too much, and our applications are difficult to extend and maintain. Finding the balance is the key to success. The first step is to identify your natural tendency as an under-abstractor or an over-abstractor. Once we know that, we can work on real-world techniques to dial in the level of abstraction that is "just right" for our applications.


I'll Get Back to You: Task, Await, and Asynchronous Methods in C# (Apr 2020)

Enterprise Developers Guild

There's a lot of confusion about async/await, Task/TPL, and asynchronous and parallel programming in general. So let's start with the basics and look at how we can consume asynchronous methods using Task and then see how the "await" operator can makes things easier for us. Along the way, we’ll look at continuations, cancellation, and exception handling.


A Banjo Made Me a Better Developer (May 2020)

Dev Around the Sun

What does a banjo have to do with software development? They both require learning. And picking up a banjo later in life showed me 3 things that I've brought into my developer life. (1) You can learn; a growth mindset removes blockages. (2) You don't have to be afraid to ask for help; experienced banjoists/developers can point you in the right direction. (3) You don't have to be perfect before you share what you've learned; it's okay to show what you have "in progress". In combination, these have made me a better banjo player and a better developer.


I'll Get Back to You: Task, Await, and Asynchronous Methods in C# (Jun 2020)

Houston .NET User Group

There's a lot of confusion about async/await, Task/TPL, and asynchronous and parallel programming in general. So let's start with the basics and look at how we can consume asynchronous methods using Task and then see how the "await" operator can makes things easier for us. Along the way, we’ll look at continuations, cancellation, and exception handling.


What's New in C# 8 Interfaces (and how to use them effectively) (Jun 2020)

Southeast Valley .NET User Group
North West Valley .NET User Group

C# 8 brings new features to interfaces, including default implementation, access modifiers, and static members. We'll look at these new features and see where they are useful and where they should be avoided. With some practical tips, "gotchas", and plenty of examples, we'll see how to use these features effectively in our code.


What's New in C# 8 Interfaces (and how to use them effectively) (Jul 2020)

Tulsa Developers Association

C# 8 brings new features to interfaces, including default implementation, access modifiers, and static members. We'll look at these new features and see where they are useful and where they should be avoided. With some practical tips, "gotchas", and plenty of examples, we'll see how to use these features effectively in our code.


I'll Get Back to You: Task, Await, and Asynchronous Methods in C# (Aug 2020)

Code PaLOUsa

There's a lot of confusion about async/await, Task/TPL, and asynchronous and parallel programming in general. So let's start with the basics and look at how we can consume asynchronous methods using Task and then see how the "await" operator can makes things easier for us. Along the way, we’ll look at continuations, cancellation, and exception handling.


Get Func-y: Understanding Delegates in C# (Oct 2020)

St Pete .NET

Delegates are the gateway to functional programming. So let's understand delegates and how we can change the way we program by using functions as parameters, return types, variables, and properties. In addition, we'll see how the built in delegate types, Func and Action, are waiting to make our lives easier. By the time we're done, we'll see how delegates can add elegance, extensibility, and safety to our programming. And as a bonus, you'll have a few functional programming concepts to take with you.


More to Come!

I have a few more recordings from 2021 presentations, and I'll post those soon. As always, if you'd like me to visit your user group, drop me a line. I'll see what I can do about being there online or in person (once in-person stuff is a bit more back to normal).

Happy Coding!

Friday, September 11, 2020

"await Task.WhenAll" Shows One Exception - Here's How to See Them All

When we use "await" in C#, we can use the typical try/catch blocks that we're used to. That's a great thing, and it makes asynchronous programming a lot easier. 

One limitation is that "await" only shows a single exception on a faulted Task. But we can use a little extra code to see all of the exceptions.

Let's take a closer look at what happens when we "await" a faulted Task and how we can take more control if we need it. 

Background

Tasks in C# are very powerful. They can be chained together, run in parallel, and set up in parent/child relationships. With this power comes a bit of complexity. One part of that is that tasks generate AggregateExceptions -- basically a tree structure of exceptions.

When we "await" a Task, it unwraps the AggregateException to give us the inner exception. This makes our code easier to program, and often there will only be one inner exception. But when there are multiple exceptions, we lose visibility to them.

This topic came up in a virtual training class that I did this past week. We were looking at parallel processing that included exception handling. We didn't have time to dig into this in the class, so I did some experimentation after.

Note: the code for this article (and the rest of the virtual training class) is available on GitHub: https://github.com/jeremybytes/understanding-async-programming.

The projects that we'll use today are "People.Service" (the web service), "TaskAwait.Library" (a library with async methods), and "TaskException.UI.Console" (a console application).

If you're completely new to Task, you can check out the articles and videos listed here: I'll Get Back to You: Task, Await, and Asynchronous Methods in C#.

Parallel Code

For this sample code, we have a console application that calls a web service multiple times. It is called for each record we want to display (for this scenario, we might only want a handful of records rather than the entire collection).

Starting the Service

If you want to run the application yourself, you'll need to start the "People.Service" web service. To do this, navigate your favorite terminal to the "People.Service" folder and type "dotnet run".


This shows the service listening at "http://localhost:9874". The specific endpoint we're using here is "http://localhost:9874/people/3" (where "3" is the id of the record that we want).

Running in Parallel with Task

The "TaskException.UI.Console" project has a single "Program.cs" file. This is where we have our parallel code. Here is an excerpt from the "UseTaskAwaitException" method (starting on line 61 of the Program.cs file):

This section sets up a loop based on the IDs of the records we want to retrieve.

The loop starts with a call to the "GetPersonAsyncWithFailures" method that returns a task (we'll look at this method in a bit). Note the "WithFailures" is there because I created a separate method to throw arbitrary exceptions.

The resulting task is added to the a task list that holds a reference to all of the tasks.

Then we set up a continuation on that task. When the task is complete, it will output the record to the console. Note that this is marked with "OnlyOnRanToCompletion", so this continuation only runs on success; it will not run if an exception is thrown.

As a last step in the loop, we add the continuation task to our list of tasks.

Because nothing is "await"ed in this block of code, all of the tasks (9 altogether) are generated very quickly without blocking. So these will run in parallel.

Outside of the loop, we use "await Task.WhenAll(taskList)" to wait for everything to complete.

This will wait for all 9 tasks to complete before continuing with the rest of the method.

The Happy Path

When no exceptions are thrown, this outputs 9 records to the console. Here is a screenshot from the "Parallel.UI.Console" project (which calls a method that does *not* throw exceptions):

This shows us all 9 records. Now let's see what happens when there are exceptions.

Throwing Arbitrary Exceptions

As mentioned above, the method we're calling for testing throws some arbitrary exceptions. Our method is called "GetPersonAsyncWithFailures". Here is an excerpt from that (starting on line 75 of the TaskAwait.Library/PersonReader.cs file):

This method has two "if" statements that throw exceptions. If the "id" parameter is "2", then we throw an InvalidOperationException. If the "id" parameter is "5", we throw a "NotImplementedException".

These exceptions are completely arbitrary. We just need multiple calls to this method to fail so we can look at the aggregated exceptions.

"await Task.WhenAll" Shows One Exception

As a reminder, here's the code that we're working with (starting on line 61 of the Program.cs file in the "TaskException.UI.Console" project):

As noted above, the "foreach" loop will run the async method 9 times (with 9 different values). Two of these values will throw exceptions.

We don't need to look for these exceptions in the continuation since we marked it with "OnlyOnRanToCompletion" -- the continuation only runs for successful tasks.

The good news is that when we "await" a faulted Task (meaning, a Task that throws an exception), the exception is automatically thrown in our current context so that we can use a normal "try/catch" block.

When we use "await Task.WhenAll(tasklist)", it will throw an exception if any of the tasks are faulted.

Since we have 2 faulted tasks here, that's exactly what happens.

Here's a bit more of the "UseTaskAwaitException" method (starting on line 76 of the Program.cs file);

The top of this block shows the "await Task.WhenAll" call. Then we have catch blocks to handle cancellation or exceptions. The "OutputException" method shows several of the properties of the exception on the console (the details are in the same "Program.cs" file if you're interested).

Let's run this method to see what happens. To run the application, navigate (using your favorite terminal) to the "TaskException.UI.Console" folder and type "dotnet run".

This will bring up the console application menu:

The code we've seen so far is part of option #1 "Use Task (parallel - await Exception)". If we press "1", then we get the following output:

The output shows us 7 records (since 2 of the 9 failed). And it shows us the "InvalidOperationException" that is thrown for ID 2.

But it does *not* show us the other exception that was thrown.

We'll dig the exception out of the Task directly and look at its values. Then it will be a little more clear what "await" is doing here.

Getting All of the Exceptions

As mentioned at the beginning, Task is very powerful. Because of the various ways that we can chain tasks or run them in parallel, Task uses what's known as an "AggregateException". This contains all of the exceptions that happen in relation to the task.

To see this we will do something a little different with the "Task.WhenAll" method call.

"Task.WhenAll" returns a Task (and this is why we can "await" it). But we can also set up our own continuation to see the details of what happened.

We have a separate method where we've done just that. Let's look at the loop on the "UseTaskAggregateException" method (starting on line 106 of the Program.cs file):

The "foreach" loop is that same as the prior method, but the "await" is different.

The continuation that is set up here is marked as "OnlyOnFaulted", so it will only run if at least one of the Tasks throws an exception. The "task.Exception" property has the AggregateException that is mentioned above.

The "OutputException" method show different parts of the exception. The details are in the "Program.cs" file if you're interested. Otherwise, we can look at the output to see what is in the exception.

Note: we are still using "await", but we are not awaiting the "WhenAll". Instead, we are awaiting the continuation task on "WhenAll". This will still pause our method until all of the tasks are complete (including this final continuation). It can get a bit confusing when mixing "await" with directly using Task.

If we re-run the console application and choose "2" this time (for "Use Task (parallel) - view AggregateException"), we get the following output:

The output shows the 7 successful records, but the exception is different from what we saw earlier.

Instead of the "InvalidOperationException", we get an "AggregateException". The exception message shows us that "One or more errors occurred", and it also concatenates the inner exception messages.

Also note that there is an "InnerException" property that has the "InvalidOperationException". This is the exception that is shown when used "await" above.

But an AggregateException also has an "InnerExceptions" property (which is a collection). If we iterate over this, we find both of our exceptions: the "InvalidOperationException" and the "NotImplementedException".

When we use a continuation to check a Task exception directly, we get more details than when we use "await". The good news is that we can iterate over these inner exceptions and pass them along to our logging system.

Changing the Order

I was wondering if the exception that "await" shows for this code is deterministic. And it turns out that it is.

As an experiment, I reversed the order of the task list before passing it to "WhenAll". Here's the code for the "await" block:

With the reverse in place, we get a different output:

The output shows us the other exception that was generated - the NotImplementedException for ID 5.

We can also reverse the task list before sending it to the continuation:

The output shows us a bit better what is happening:

By looking directly at the AggregateException, we can see that the "InnerException" is now the one related to ID 5 (which is shown when we "await" things above).

Looking at the "InnerExceptions" collection, we see that the order is reversed. The ID 5 exception is first, and the ID 2 exception is second.

So when it comes to awaiting "Task.WhenAll", it appears that the exception we ultimately get is based on the order of the Tasks that are passed to the "WhenAll" method. Fun, huh?

Is This Important?

So how much do we need to care about this? The typical answer I would give is "probably not much". Tasks are extremely powerful, but we often do not need that power. And that's why "await" exists.

"await" gives us the 95% scenario. A common scenario I have is that I need to call an asynchronous method, wait for it to finish, do something with the result, and handle exceptions. If I only need to wait for one method at a time, then "await" works perfectly for this.

When we need to step outside of this scenario, it's good to understand how to use Task more directly. By understanding the process of setting up continuations and unwrapping AggregateExceptions, we can take more control of the situation when we need it.

Often we only care that "an exception happened". This lets us know that our application is in a potentially unstable state, and we need to handle things accordingly. We do not care about all of the details of what went wrong, and "await" is all we need. Whether we need to dig deeper depends on the needs of the users and the application.

If you want more information about using Task and await, you can check out the articles and video series on my website: I'll Get Back to You: Task, Await, and Asynchronous Methods in C#.

Happy Coding!


Tuesday, March 1, 2016

New Video: Task & Await Recorded at NDC London 2016

As mentioned previously, I had an awesome time at NDC London back in January. One great thing about NDC is that they record the sessions and make them available. Today, the recording of my session was posted.
I’ll Get Back to You: Task, Await, and Asynchronous Methods in C# - Jeremy Clark
There's a lot of confusion about async/await, Task/TPL, and asynchronous and parallel programming in general. So let's start with the basics and look at how we can consume asynchronous methods using Task and then see how the "await" operator can makes things easier for us. Along the way, we’ll look at continuations, cancellation, and exception handling.
Watch it on Vimeo: I'll Get Back to You.

Or watch it here:


Slides and code samples are available here: Session Materials: I'll Get Back to You.

This has become one of my more popular topics this year. And I've had a great response. Here are some of the comments from the Twitterverse from NDC London:










The total count on the evaluation cards: 157 Green, 7 Yellow, and 0 Red.

Look for Your Chance
If you'd like to see this presentation LIVE, be sure to check out my speaking schedule. You can see my upcoming confirmed events on my homepage: www.jeremybytes.com. I have several proposals out there, so be sure to check back for updates as events are confirmed.

If you're in California, I'll be at user groups in Los Angeles, Berkeley, and Fresno over the next 6 weeks. And I'll also be presenting this topic at KCDC in June.

And, of course, I have lots of other topics that I love to talk about. I hope to see you at an event soon!

Happy Coding!

Wednesday, September 9, 2015

New Video: Cancellation Basics with Task and Await

Cancellation gets a bit tricky because we end up passing around a cancellation token. But there's a problem: if we create a cancellation token directly, we can't change it. Fortunately, there's a easy solution: use a cancellation token source to manage the token.

Part 4 of my video series on Task and Await is now available. This time we look at the basics of cancelling an asynchronous process. To get code, articles, and other videos, check here: Exploring Task, Await, and Asynchronous Programming.

Watch the video on YouTube: Task and Await in C# - Part 4: Basic Cancellation
Or read the article: Task and Await - Basic Cancellation

Or watch it here:



If you've missed the previous episodes, get them all together in the playlist:




If you've got suggestions for other topics, please leave a comment. I'm always looking for good stuff to talk about.

Happy Coding!

Thursday, September 3, 2015

New Video: Basic Exception Handling with Task and Await

Part 2 of my video series on Task and Await in C# is now available. Check out the code downloads and articles here: Exploring Task, Await, and Asynchronous Methods.

This time we take a look at what happens if our asynchronous method throws an exception. This is pretty easy to deal with when using the "await" operator (we just add a "catch" block). And if we need to take full control over the situation, we can use the various continuation options that are available with Task.

Direct link: Task and Await in C# - Part 2: Basic Exception Handling

Or watch it here:


Look forward to more episodes coming soon.

Happy Coding!

Tuesday, September 1, 2015

New Video: Consuming Asynchronous Methods

I just started a new video series on Task and Await in C#. This is based on my recent series of articles Exploring Task, Await, and Asynchronous Methods.

Part 1 is currently available. This is all about how to consume an asynchronous method. We look at both using Task manually and using await to let the compiler do the heavy lifting. This will give us a good start as we head into looking at exception handling, cancellation, and finding the gotchas along the way.

Direct link: Task and Await in C# - Part 1: Consuming Asynchronous Methods

Or watch it here:

You can get the code samples and view the printed articles here: Exploring Task, Await, and Asynchronous Methods.

Coming Soon:
Part 2: Basic Exception Handling now available!
Part 3: IsFaulted, IsCanceled, and Task.Status now available!
Part 4: Basic Cancellation now available!

Watch the entire series: Task and Await in C#

Happy Coding!

Tuesday, January 13, 2015

Task and Await: Basic Cancellation

In our exploration of Task, await, and asynchronous programming, we want to take a look at the concept of cancellation. When we kick off a long-running process, we may decide that we want to cancel the operation before it completes.

We're still looking at things from the consumer side: methods that make calls to asynchronous methods. In future articles, we'll see how to write our own async methods.

We'll continue with our previous example project which is available on GitHub: https://github.com/jeremybytes/using-task. The articles that describe the project up to this point are available here: Exploring Task, Await, and Asynchronous Methods.

The completed code for this article is in the 09-CancellationBasics branch.

So let's jump into some code!

Cancellation in the Asynchronous Method
For our asynchronous method, we're using the "Get()" method on the "PersonRepository" class. This method does not support cancellation. So we'll add a parameter to accept a "CancellationToken" and use it in our code:

from PersonRepository.cs

There are a couple of things to note here. First, we have a default value for our "cancellationToken" parameter. This is because we don't want to break our existing method calls that don't pass a cancellation token.

In this case, if the "Get()" method is called without any parameters, then the "cancellationToken" will be a instantiated with the CancellationToken constructor. Now, as we saw when we looked at "UI Considerations When Using Asynchronous Methods", calling the constructor like this will create a cancellation token that is in the "not canceled" state.

Also, as we saw in the update in that same article, we swapped that out to "CancellationToken.None". Unfortunately, that's not something that we can use for a default parameter assignment, so we have to use the constructor here.

The second thing to note is that after waiting for 3 seconds, we call "ThrowIfCancellationRequested()" on our cancellation token. This does the following:
  • It sets the "IsCanceled" and "Status" properties on our Task to "true" and "TaskStatus.Canceled", respectively.
  • It throws an exception. This is a special exception that is automatically handled (sort of).
  • It does not set the "IsFaulted" state on our Task.
  • It does not add the exception to the AggregateException of the Task (the property we saw when we looked at exception handling).
We'll dive into this topic a bit deeper when we get into creating our own asynchronous methods. For now, let's head back to our calling code.

Cancelling a Method with Task
Here's how we left our method that interacts with the Task returned from the "Get()" method:


After getting a Task back from the "Get()" method, we add a continuation that runs on the UI thread. This currently checks for a faulted state (if there's an exception thrown) and checks for successful completion.

To test the cancellation functionality, we'll just hard-code a cancellation token:


When we pass in "true" to the CancellationToken constructor, it creates a cancellation token that is in the "cancellation requested" state. (We'll get rid of this hard-coded value a bit later).

Cancellation Requested
So now when we run our application and click our "Task" button, it will call the "Get" method with cancellation requested.

One thing that is very important to note about using cancellation tokens is that they do not directly affect processing -- meaning, they don't stop operations or cause an abort. Instead, the cancellation token is more of a flag; it gives us a way to let the asynchronous method know that we want to cancel the operation, but it's up to the asynchronous method itself to decide how to handle this request.

In our case, the "Get()" method will still have the 3 second delay. This is because the delay is in the method before we check the cancellation token at all. After the delay is done, then the method checks the cancellation token state and throws the exception if cancellation is requested.

Cancellation Functionality
So let's run our application to see what happens. When we click the button, it is disabled while we wait:


Then the button is re-enabled:


Nothing else happens. And this makes sense if we review our method. We check for "faulted" and show a message box. We check for "RanToCompletion" and populate the list box. But we don't have anything that checks for the "canceled" status. So we end up skipping both of the current "if" blocks and just re-enable the button.

Checking for Cancellation
It's easy enough to add functionality to check for cancellation. When we updated our continuations, we saw that Task has an "IsCanceled" property. We can easily use this in our continuation.


If "IsCanceled" it true, then show a message to the user. And when we run our application, we see just that:


Cleaning Things Up
Now our code is getting a bit hard to read. One thing we can do to keep things consistent is to check the "Status" property for all 3 of our "if" blocks (we saw the TaskStatus options when we updated our continuations as well).


This is a little bit better. At least we're checking the same property in all of our options now. But we can take this a step further and turn this into a "switch" statement:


To me, this is much easier to follow. We can see what happens with each of the different states. And it's actually a bit more correct, because we probably wanted to have "if / else if / else" in our previous code to make things mutually exclusive. With this "switch" statement, this is much more clear.

Note: There are a lot of other possible values for Status, but we're sticking with just the ones that we care about here. These are the statuses that we get after a Task has stopped running, which is the state that we should have since we are in a continuation.

Hard-Coded Cancellation
This is fine for test code, but we probably don't want to leave the hard-coded cancellation in place. We'll want to make it so that we can cancel from the UI.

But before we do that, let's get our "await" method working with cancellation.

Cancelling a Method with Await
Here's how we left our method that uses "await":


And we can change the "Get()" call so that is passed in a hard-coded cancellation token just like we did with our Task method.


Now let's run the application to see what happens.


Interesting. We get a message box. This means that it looks like we're hitting the "catch" block of our method. Let's verify that by setting a breakpoint and checking the exception:


This shows that we do hit the "catch" block, and the exception is an "OperationCanceledException". So when we use the "await" keyword, the special cancellation exception gets surfaced on our UI thread.

But since this is a specific exception, it's easy enough to add another "catch" block just for this:


Now we have a "catch" block that just handles the cancellation and another "catch" that handles all of our other exceptions.

When we run our application, we see the new message box (note the "Canceled" in the caption):


As usual, we see that things are a bit easier to deal with when we use "await" rather than dealing with "Task" directly. So if we have a situation where we can "await" a method, that code will be a bit easier to read and write.

Hard-Coded Cancellation
As with our "Task" method, our "await" method has a hard-coded cancellation token. This is fine for testing our functionality, but it's not what we want in our final application. Instead, we want to give our users a way to cancel the operation from the application while it is running.

Managing a Cancellation Token
The first thing that we'll do is add another button to our UI. This will be a cancellation button, and we want this to be disabled when our application starts. That's because we only want our button to be enabled when we have something that can be canceled.

Here's our updated UI:


CancellationTokenSource
Now we might be inclined to create a CancellationToken and then set its value when our cancel button is clicked. But this doesn't work. When I was first working with "Task", this is what I struggled with.

The problem is when we create a CancellationToken directly, it is not modifiable. We can check its state, but this is a read-only property. And there are no methods for changing the state.

Instead, we need to create a CancellationTokenSource. This is an object that we can use to manage a cancellation token.

We'll create a class-level field for our CancellationTokenSource. This way, we'll be able to access the object inside our various methods of our class:


We're just declaring the field here. We'll see where we instantiate it in just a bit.

Setting the Token to Canceled
Inside the "Click" event handler for our cancel button, we'll set our token to a canceled state:


This will set the "IsCancellationRequested" property of the cancellation token to "true". I would highly recommend taking a look at the methods and properties on both CancellationTokenSource and CancellationToken. There are a lot of options available. We're just using a few of the methods and properties here.

The other thing that we do in the method is disable the "Cancel" button. If we click it once, there's no reason for us to click it again. As a reminder, our method will not immediately return; we still need to wait for that 3 second delay to complete before the cancellation token is checked. By disabling the button, we give the user a visual clue that it was actually clicked.

Using the Token with Task
Now that we have a CancellationTokenSource, we can start to update our methods. We'll start with the method that uses Task.

Here are the bulk of the changes:


First, we create a new instance of the CancellationTokenSource and assign that to our field. The reason that I'm doing this is because we'll have similar code in the "await" method, and we're sharing the "tokenSource" field. I want to make sure that we get a new token source (which has an uncanceled token) at the start of each method.

Next, we enable the "CancelButton". This will light up the button in the UI so that we can request cancellation.

Then we pass the "Token" property of our CancellationTokenSource to our "Get" method. The "Token" property is a cancellation token, and this is how we get our live token to our asynchronous method.

Finally, at the bottom of our method, we disable the cancellation button in the UI (since the process is complete):


Running the Code
With this in place, let's run our application to see what happens. After we click the "Fetch" button, we see that the "Cancel" button is enabled:


Then if we click the "Cancel" button before our 3 seconds is up, we see that the "Cancel" button is disabled, but we're still waiting on the "Get()" method.


Then we get the cancellation message that we saw earlier:


After we clear this message, the "Fetch" button will re-enable just like we saw previously.

And if we do not cancel, things still run as expected:


Final Task Method
Here's our final method that uses "Task" with the cancellation functionality in place:


Now let's take a look at our "await" method.

Using the Token with Await
As you can imagine, we'll do something similar with our "await" method: use the CancellationTokenSource and enable/disable buttons as appropriate. Here's the final method:


Just like with our "Task" method, we create a new instance of the "CancellationTokenSource". This will ensure that things are set back to an initial state. We use the "Token" property as a parameter for the "Get" method. And we just enable our "Cancel" button and disable it again in our "finally" block.

And as you can imagine, this functions similar to our other method. First, when we click the "Fetch" button, our "Cancel" button will enable:


Then if we click our "Cancel" button, it will be disabled while we wait for the "Get()" method.


Then we get our cancellation message.


And, of course, our functionality works just the same as before if we do not cancel it.

Wrap Up
The biggest stumbling block I had when I started working with "Task" was understanding that you couldn't use a "CancellationToken" directly. Instead, we need to work with a "CancellationTokenSource". This object has a property, "Token", which is a cancellation token. And we set the state of the token by calling methods on the CancellationTokenSource.

And as we saw, when an asynchronous method is canceled, we need to pay attention to the state of the Task that returns. If we're using "Task" directly, then we can check the "Status" or the "IsCanceled" properties to know if the Task has been canceled.

And if we're using "await", then we can check for an "OperationCanceledException". This will let us know that the operation was canceled before it ran to completion.

Overall, canceling an asynchronous method is not all that complex. But there are a few things that aren't obvious. By doing some exploration, we can see how things work and how we can take advantage of these features in our own applications.

Happy Coding!