Returning null from Task-returning methods in C#
Take a look at the following code and guess what happens when the Main method gets invoked:
When Main runs, await AsyncFoo() executes without issue, but await NonAsyncFoo() throws a NullReferenceException :
If you're new to asynchronous C# like me, this can be surprising because the returned value is the same; the only difference on the surface is the async keyword.
In this blog post, we'll understand this behavior and the difference between returning null from an async vs. non- async method in C#.
Returning null from a non-async Task-returning method
We can return null from a method that returns a Task because Task is a reference type. In our previous example, we return null from NonAsyncFoo() . But, awaiting null isn't legal, so await NonAsyncFoo() throws a NullReferenceException .
One way to prevent this NRE while still returning null is to check that the result of the method is not null and await a valid Task if it is. We can do this with the null-coalescing operator; for example:
However, this isn't ideal. First, it requires every caller that awaits the method to know that we might return null . Second, returning null from a non- async Task -returning method doesn’t really make sense. As Stephen Cleary explains in this StackOverflow post:
Task represents the execution of the asynchronous method, so for an asynchronous method to return a null task is like telling the calling code "you didn't really just call this method" when of course it did.
Instead, we need to ensure that Task-returning methods return a Task that can be awaited. In our case, we can use Task.FromResult() to wrap null :
Now, we can safely await our method:
If we were just returning a Task and not Task<T> , we could return Task.CompletedTask instead:
Returning null from an async Task-returning method
The async variant of our example method is valid at runtime; it won't throw a NRE. This confused me at first: how does making this method async let us safely return null ? After all, the returned value remains the same. This behavior can be explained by the method transformation that the async keyword opts us into.
In addition to making the await keyword available within the method body, adding the async keyword to a method signature causes the compiler to transform the method into a state machine . The transformed code wraps the return value and any exceptions in a Task<T> .
To demonstrate how this works, let's say we have the following non- async method that returns a Task<string> :
Using the dotPeek decompiler to view the compiler-generated source, we can see that the compiler generates almost identical code:
Now, let's make this method async :
The compiler-generated code for this method looks much different:
The content of Foo is transformed into a state machine. The full details and mechanics of the state machine are not within the scope of this article, but here's part of the generated state machine class that is instantiated at the beginning of Foo :
The code that we’re concerned about is on the last line of the state machine’s MoveNext method:
The AsyncTaskMethodBuilder 's SetResult method creates the Task and sets the result to be "Foo, bar, baz" . (If you're interested in going deeper and seeing how the Task is created, you can find the method's implementation in the .NET reference source code here .)
This Task is subsequently returned on the last line of the transformed Foo :
To recap, the compiler transforms an async method into a state machine, which wraps the result in a Task and returns it to the caller.
Returning null from non- async Task -returning methods returns a null Task , which is almost never what a caller wants and invites NREs. Instead, ensure that all Task -returning methods return a Task ; you can use Task.FromResult(null) in place of null .
We don’t have to worry about manually creating a Task when we mark a method as async . The compiler transforms async methods into a state machine that wraps our return value in a Task for us.
Come connect with me on LinkedIn , Twitter , and GitHub !
If you found this post helpful, please consider supporting my work financially:
Thanks to Jacob Carpenter for helping me understand the async method transformation and providing feedback on an early draft of this post.
Subscribe for the latest news
Sign up for my mailing list to get the latest blog posts and content from me. Unsubscribe anytime.
This browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
Async return types (C#)
- 3 contributors
Async methods can have the following return types:
- Task , for an async method that performs an operation but returns no value.
- Task<TResult> , for an async method that returns a value.
- void , for an event handler.
- Any type that has an accessible GetAwaiter method. The object returned by the GetAwaiter method must implement the System.Runtime.CompilerServices.ICriticalNotifyCompletion interface.
- IAsyncEnumerable<T> , for an async method that returns an async stream .
For more information about async methods, see Asynchronous programming with async and await (C#) .
Several other types also exist that are specific to Windows workloads:
- DispatcherOperation , for async operations limited to Windows.
- IAsyncAction , for async actions in UWP that don't return a value.
- IAsyncActionWithProgress<TProgress> , for async actions in UWP that report progress but don't return a value.
- IAsyncOperation<TResult> , for async operations in UWP that return a value.
- IAsyncOperationWithProgress<TResult,TProgress> , for async operations in UWP that report progress and return a value.
Task return type
Async methods that don't contain a return statement or that contain a return statement that doesn't return an operand usually have a return type of Task . Such methods return void if they run synchronously. If you use a Task return type for an async method, a calling method can use an await operator to suspend the caller's completion until the called async method has finished.
In the following example, the WaitAndApologizeAsync method doesn't contain a return statement, so the method returns a Task object. Returning a Task enables WaitAndApologizeAsync to be awaited. The Task type doesn't include a Result property because it has no return value.
WaitAndApologizeAsync is awaited by using an await statement instead of an await expression, similar to the calling statement for a synchronous void-returning method. The application of an await operator in this case doesn't produce a value. When the right operand of an await is a Task<TResult> , the await expression produces a result of T . When the right operand of an await is a Task , the await and its operand are a statement.
You can separate the call to WaitAndApologizeAsync from the application of an await operator, as the following code shows. However, remember that a Task doesn't have a Result property, and that no value is produced when an await operator is applied to a Task .
The following code separates calling the WaitAndApologizeAsync method from awaiting the task that the method returns.
Task<TResult> return type
The Task<TResult> return type is used for an async method that contains a return statement in which the operand is TResult .
In the following example, the GetLeisureHoursAsync method contains a return statement that returns an integer. The method declaration must specify a return type of Task<int> . The FromResult async method is a placeholder for an operation that returns a DayOfWeek .
When GetLeisureHoursAsync is called from within an await expression in the ShowTodaysInfo method, the await expression retrieves the integer value (the value of leisureHours ) that's stored in the task returned by the GetLeisureHours method. For more information about await expressions, see await .
You can better understand how await retrieves the result from a Task<T> by separating the call to GetLeisureHoursAsync from the application of await , as the following code shows. A call to method GetLeisureHoursAsync that isn't immediately awaited returns a Task<int> , as you would expect from the declaration of the method. The task is assigned to the getLeisureHoursTask variable in the example. Because getLeisureHoursTask is a Task<TResult> , it contains a Result property of type TResult . In this case, TResult represents an integer type. When await is applied to getLeisureHoursTask , the await expression evaluates to the contents of the Result property of getLeisureHoursTask . The value is assigned to the ret variable.
The Result property is a blocking property. If you try to access it before its task is finished, the thread that's currently active is blocked until the task completes and the value is available. In most cases, you should access the value by using await instead of accessing the property directly.
The previous example retrieved the value of the Result property to block the main thread so that the Main method could print the message to the console before the application ended.
Void return type
You use the void return type in asynchronous event handlers, which require a void return type. For methods other than event handlers that don't return a value, you should return a Task instead, because an async method that returns void can't be awaited. Any caller of such a method must continue to completion without waiting for the called async method to finish. The caller must be independent of any values or exceptions that the async method generates.
The caller of a void-returning async method can't catch exceptions thrown from the method. Such unhandled exceptions are likely to cause your application to fail. If a method that returns a Task or Task<TResult> throws an exception, the exception is stored in the returned task. The exception is rethrown when the task is awaited. Make sure that any async method that can produce an exception has a return type of Task or Task<TResult> and that calls to the method are awaited.
The following example shows the behavior of an async event handler. In the example code, an async event handler must let the main thread know when it finishes. Then the main thread can wait for an async event handler to complete before exiting the program.
Generalized async return types and ValueTask<TResult>
An async method can return any type that has an accessible GetAwaiter method that returns an instance of an awaiter type . In addition, the type returned from the GetAwaiter method must have the System.Runtime.CompilerServices.AsyncMethodBuilderAttribute attribute. You can learn more in the article on Attributes read by the compiler or the C# spec for the Task type builder pattern .
This feature is the complement to awaitable expressions , which describes the requirements for the operand of await . Generalized async return types enable the compiler to generate async methods that return different types. Generalized async return types enabled performance improvements in the .NET libraries. Because Task and Task<TResult> are reference types, memory allocation in performance-critical paths, particularly when allocations occur in tight loops, can adversely affect performance. Support for generalized return types means that you can return a lightweight value type instead of a reference type to avoid additional memory allocations.
.NET provides the System.Threading.Tasks.ValueTask<TResult> structure as a lightweight implementation of a generalized task-returning value. The following example uses the ValueTask<TResult> structure to retrieve the value of two dice rolls.
Writing a generalized async return type is an advanced scenario, and is targeted for use in specialized environments. Consider using the Task , Task<T> , and ValueTask<T> types instead, which cover most scenarios for asynchronous code.
In C# 10 and later, you can apply the AsyncMethodBuilder attribute to an async method (instead of the async return type declaration) to override the builder for that type. Typically you'd apply this attribute to use a different builder provided in the .NET runtime.
Async streams with IAsyncEnumerable<T>
An async method may return an async stream , represented by IAsyncEnumerable<T> . An async stream provides a way to enumerate items read from a stream when elements are generated in chunks with repeated asynchronous calls. The following example shows an async method that generates an async stream:
The preceding example reads lines from a string asynchronously. Once each line is read, the code enumerates each word in the string. Callers would enumerate each word using the await foreach statement. The method awaits when it needs to asynchronously read the next line from the source string.
- Process asynchronous tasks as they complete
- Asynchronous programming with async and await (C#)
The .NET documentation is open source. Provide feedback here.
Submit and view feedback for
Posted on Apr 1, 2022
Async gotcha: returning Task.FromResult or Task.CompletedTask
So you're implementing this async handler or interface method that doesn't have anything to await, and you write something like
But now the C# compiler emits a stern warning: "CS1998: you're using async , so you should be awaiting stuff! find something to await!"* Well, you have nothing to await, but you must still return Task. You remove async and return Task.FromResult(true) instead, as top-rated answers by high-reputation members on Stackoverflow recommend. Job done.
As long as HandleAsync is always called and awaited immediately, all is fine.
But a dark threat lurks in the shadows. Sometimes a try-catch fails to catch an exception.
Because HandleAsync is actually synchronous, any exception propagates as soon as it's called, not when it's awaited. This is surprising to most C# programmers. A synchronous method that returns a Task is not what you expect, especially when it's called SomethingAsync. Surprising, unexpected behavior leads to bugs. Bugs lead to unhappy customers and developers alike.
So what should we do instead?
One option would be to disable warning CS1998, but it may point out cases where a method just shouldn't return a Task in the first place. Probably the best thing would be to mark the function as async and await Task.FromResult :
Or if it's a plain Task , awaiting Task.CompletedTask .
Is this slower? Not much - awaiting a completed task does almost nothing, and the runtime might be able to recognize this common pattern and elide all the overhead - but choosing the async-looking-but-actually-synchronous behavior for hypothetical performance reasons firmly falls in the category " evil premature optimization ", in my opinion.
*Actually, it says
warning CS1998: This async method lacks ‘await’ operators and will run synchronously. Consider using the ‘await’ operator to await non-blocking API calls, or ‘await Task.Run(…)’ to do CPU-bound work on a background thread.
Top comments (0)
Templates let you quickly answer FAQs or store snippets for re-use.
Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink .
Hide child comments as well
For further actions, you may consider blocking this person and/or reporting abuse
Karen Payne - Sep 17
Introdução ao .NET com Visual Studio Code
Gabriel Alves - Oct 7
What is the best way to copy an array?
John Nyingi - Oct 7
.Net with MySql
Joao Costa - Oct 7
Once suspended, asik will not be able to comment or publish posts until their suspension is removed.
Once unsuspended, asik will be able to comment and publish posts again.
Once unpublished, all posts by asik will become hidden and only accessible to themselves.
If asik is not suspended, they can still re-publish their posts from their dashboard.
Once unpublished, this post will become invisible to the public and only accessible to André Slupik.
They can still re-publish the post if they are not suspended.
Thanks for keeping DEV Community safe. Here is what you can do to flag asik:
asik consistently posts content that violates DEV Community's code of conduct because it is harassing, offensive or spammy.
Unflagging asik will restore default visibility to their posts.
We're a place where coders share, stay up-to-date and grow their careers.
- Blazor WASM 🔥
- ASP.NET Core Series
- GraphQL ASP.NET Core
- ASP.NET Core MVC Series
- Testing ASP.NET Core Applications
- EF Core Series
- HttpClient with ASP.NET Core
- Azure with ASP.NET Core
- ASP.NET Core Identity Series
- IdentityServer4, OAuth, OIDC Series
- Angular with ASP.NET Core Identity
- Blazor WebAssembly
- .NET Collections
- SOLID Principles in C#
- ASP.NET Core Web API Best Practices
- Top REST API Best Practices
- Angular Development Best Practices
- 10 Things You Should Avoid in Your ASP.NET Core Controllers
- C# Back to Basics
- C# Intermediate
- Design Patterns in C#
- Sorting Algorithms in C#
- Docker Series
- Angular Series
- Angular Material Series
- HTTP Series
- .NET/C# Author
- .NET/C# Editor
- Our Editors
- Leave Us a Review
- Code Maze Reviews
Difference Between Returning and Awaiting a Task in C#
Posted by Ahsan Ullah | Updated Date Mar 2, 2023 | 2
In this article, we are going to talk about the difference between returning and awaiting a Task in an async method in C#.
The async/await mechanism is an abstraction over asynchronous code that allows us to write asynchronous programs like a synchronous routine. This magical convenience comes through a compiler-driven transformation under the hood. However, this also leads to certain gotchas that we need to be aware of.
For example, returning a Task from a method and awaiting a Task may appear as equivalent operations but in reality, that’s not always true. Let’s see how they differ in various aspects.
Major Differences Between Returning and Awaiting a Task
To begin, let’s consider a simple routine that returns plain Task :
And its functional equivalent using async/await :
This task simply emulates an operation running for a specific duration. It also throws an exception if the duration exceeds a certain limit. Let’s dig more to find the potential differences between these two versions.
One obvious difference is due to the state machine mechanism of async/await . Awaiting an async method, behind the scene, deals with extra compiler-generated types, allocation of a Task object, and lifting locals to the internal state machine. All these lead to extra memory overhead and pressure on the garbage collector. However, in most cases, this overhead is not significant enough to outweigh the benefits of await .
Probably the most important difference lies in exception propagation.
The execution flow in the non-async Task is nothing special. We encounter exceptions just like any usual synchronous routine:
As we try to get the task with a duration above the maximum value, we encounter an exception along the way. But the story is different in the case of async version:
This time the exception doesn’t stop us from getting the task instance. It shows up only when we execute the task (by calling await task , task.Wait() , task.Result etc.).
This behavior demands a closer look at the async/await mechanism. An async method can be seen as a set of code chunks with resumption/cancellation capability at chunk boundaries. So when we use an await inside, we are setting a resumption point. When the method execution happens, it flows synchronously until it reaches an awaited point and resumes at a later time when await completes. The compiler provides this mechanism under the cover by generating additional constructs called “State Machine”.
The body of an async method is not a truly live code and the true execution flow does not happen the way we see the code. The state machine captures exceptions from the visual code and places them in the returned task. So we just get a Task instance when we call the method and do not reach those exception points in real-time. Instead, the exception emerges when the task really starts executing that particular chunk of code. And if an exception occurs, the task will be flagged as faulty.
This may sound like a pitfall of async . But it actually aligns with the perspective of Task – it’s about the work to be done (in the future), not about how things are being done within the work. So, the exception which is part of the work, should not interfere with defining the work.
Note: we have used a pre-conditioned exception in examples for the sake of simplicity. But this exception can be a genuine I/O or network exception within the code as well. As long as we talk about the natural interpretation of a Task , it does not matter what exceptions we deal with.
Another major point of concern arises when we invoke an asynchronous method of a disposable object:
This simple data-reader class provides an async ReadAsync method. Such a class typically implements IDisposable to handle unmanaged resources before disposal. In our case, we just set a _disposeInvoked flag to better understand the dispose execution flow. For brevity, we opt out of a full-fledged dispose pattern.
Inside the ReadAsync method, we provide a conditional result based on the disposed state.
Now, let’s prepare two top-level tasks that call this ReadAsync within a using block:
When we execute the tasks:
We see two different outputs. As the result states, the ReadTaskAsync version waits for the reader.ReadAsync task to finish before disposing of the reader instance. In contrast, the ReadTask version immediately disposes the reader instance on leaving using block (with an incomplete task), and the returned Task finishes at the caller’s end. No wonder, the latter method is prone to unwanted side effects (and exceptions) due to possible attempts of accessing already disposed resources.
Other Differences Between Awaiting and Returning a Task
There are some other differences that are platform-specific and less common occurrences.
Our next point of difference refers to one key principle of asynchronous programming – “ Don’t block in async code “.
Synchronous execution of async methods often causes deadlocks in UI or legacy ASP.NET applications (not in Console or ASP.NET Core):
We get an async ReadContentTaskAsync() task and want a synchronous retrieval of the result inside ReadContent method. This code causes a deadlock if we are in UI applications e.g. WinForm, WPF, etc. To better understand this deadlock, let’s shed light on how await handles contexts.
When we await an incomplete Task , the current context gets captured. When the await completes, the rest of the method is executed within the captured context. If the context is locked for some reason, the continuation will wait for the context to be released first. Here goes the second piece of the puzzle – the default context ( SynchronizationContext ) permits only one chunk of code to run at a time. So when we call task.Result , the current thread gets synchronously blocked and waits for the task to complete. However, within the task execution flow, when the captured context wants to resume execution after await , it finds itself locked by calling thread. That means both the calling thread and the context are waiting for each other, causing a deadlock.
Returning a plain Task without await in this case, will avoid an extra occurrence of captured context and hence a less chance of deadlocking. Nonetheless, this is more of a problem with bad mixing of sync and async code than with the async/await .
In procedural programming, we sometimes use a context object that is accessible anywhere in the code (or class). This helps in maintaining cleaner API by sharing data across different methods without needing explicit parameters in every method. However, in a multi-threaded async program, this can be tricky to manage as we need to match the current execution flow and the current context object. AsyncLocal serves this exact purpose.
By definition , AsyncLocal represents ambient data that is local to a given asynchronous control flow, such as an asynchronous method. However, this contextual data flows down the asynchronous call stack, not the opposite:
We are using a context object at a top-level async task, changing its value within the parent scope, and then changing the value inside a child task. As long as we stay within the parent task’s execution body, the parent’s context value persists across the await boundaries. This value also propagates to the child task’s execution flow along the way. However, any change of value inside the child async task does not reflect on the parent’s context. That’s why we get the same parent value even after the child execution completes. This is the expected asynchronous behavior – each asynchrony will hold its own local context so that multi-threaded calls will not interfere with each other.
However, if we use a non-async child task, it behaves just like a normal synchronous method:
This time, value changes inside the child affect the parent Task as well.
When To Use Returning Versus Awaiting
We now know returning and awaiting a task does not always produce the same outcome. The async method, because of its easy-to-read natural code flow and truly asynchronous exception-handling semantics , is the recommended approach in most cases. However, when the method simply returns a passthrough Task or is merely an overload of another async method, we can consider using a non-async task:
For example, we have an async CalculateTaskAsync method that expects a roundUp parameter. Now we want a convenient overload that just passes a default value for the roundUp flag. There is no added benefit of await in this overloaded method.
And last but not least, we should always avoid synchronous blocking of an asynchronous task . If we ever need such a thing, it’s better to implement the task as a non-async task.
In this article, we have learned a few differences between returning and awaiting a task in asynchronous methods. In general, we should use async/await when we really want an asynchronous Task and use plain Task when no real asynchronous part exists in the method body.
Join our 20k+ community of experts and learn about our Top 16 Web API Best Practices .
Async Task not returning results
I have around 10-15 ecto queries which I want to run async in my API code. I am using Task.async and Task.yeild_many
Following is the code for async task -
I get the tasks in my main function as -
And my task helper code is given below -
Each tasks are ecto queries. The issue is the tasks are behaving randomly. Sometimes they return results, sometimes they don’t. But there was no time when all the tasks have return results (for the representational purpose I have written 3 tasks, but I have around 10-15 async tasks). I ran the code synchronously, and it returned the correct results (obviously). I tried changing the pool_size in config to 50 for Repo, but to no avail.
Can someone please help me out with this? I am quite stuck here.
Are all tasks calling the same function?
A batch of them does, other batch of them calls the other function.
The problem might be out of order results but let’s not check for that just yet.
Have you tried using Task.async_stream for each batch of tasks that call the same function?
If you only ever call 2 different functions then that would be 2 such calls.
How long does that take?
Task.yield_many/2 has a default timeout of 5 secs - maybe that isn’t enough time for Ecto to finish all the queries.
The issue is the tasks are behaving randomly.
I wouldn’t necessarily expect Ecto + RDBMS to behave in a deterministic fashion (order of work finished). So the apparent randomness wouldn’t be too surprising if the timeout is too short.
The other advantage of Task.async_stream/5 is that the level of concurrency can controlled with :max_concurrency (it defaults to the number of online cores).
There is also Task.Supervisor.async_stream_nolink/6 .
- Back to top
© Copyright Elixir Forum | Terms | Privacy & Cookies
Search code, repositories, users, issues, pull requests...
We read every piece of feedback, and take your input very seriously.
Use saved searches to filter your results more quickly.
To see all available qualifiers, see our documentation .
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement . We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Asp.NET - hangs returning Task Result #337
Jojoshua commented Jun 22, 2018
kblok commented Jun 22, 2018
Sorry, something went wrong.
kblok commented Jun 23, 2018
Jojoshua commented jun 23, 2018.
Meir017 commented Jun 23, 2018
Kblok commented sep 1, 2018, jojoshua commented sep 1, 2018, kblok commented sep 1, 2018 • edited, jojoshua commented sep 2, 2018.
No branches or pull requests
Returning async result
Is it possible to return the async task result from some calling method without awaiting the task or having the calling method be async itself? e.g.
This kind of thing is common in JS where a function can forward up the Promise returned by an async function without the overhead of awaiting it and fulfilling its own promise.
oh... then hopefully it's obvious that you'd want to write something like, await outerWrapper(...)
I’m not sure I understand your question. Your code snippet includes an AsyncTask identifier, but that’s not an Apple thing. Is that from some third-party SDK? Or perhaps it’s a proposal for what you’d like to see?
My best guess is that you want some automatic way to convert an async task into a promise. If so, that’s depends on what promise library you’re using. The only Apple one, Combine, does not support this (although it does support going the other way ). However, you should be able to build a general solution that wraps any async function as single-value publisher. For example, a quick search of the ’net turned up Calling async functions within a Combine pipeline .
Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com"
@ eskimo ....my bad. AsyncTask is a proposal for what I'd like to see if it didn't already exist.. It's just a placeholder for some declaration that would let the compiler know that await outerWrapper() is syntactical ok. I guess I could also write it like:
where it still just avoids using the await keyword. But, in my mind, if the async keyword is used in the function signature, it's an error not to await any asynchronous tasks it calls (which is a common mistake in JS).
I'm not really looking to convert the task at all. I'm more looking to skip an await call in a call stack when a given function, outerWrapper in this case, only needs to await once as part of (each of) its return statement(s). In this case, if we can ensure the caller of outerWrapper makes an await call, then the task from theAsyncFunc can propagated up the call stack, and outerWrapper doesn't technically do anything asynchronously in its own function body.
Another way I'm thinking about it is returning an async let:
But it sounds like each function in the call stack must be async and must await all the async functions it calls. I can imagine this makes managing the call stack easier for debug purposes. So I get it.
Kind of a side note, as I play with this more, it looks like I'm going to do this a lot because my SwiftUI view models currently have an inheritance hierarchy, so that they can't be actors until the whole code base is overhauled.
What am I missing? Is there or can there be a way to run the Task's body on the main thread but send off the await ed call to background threads, like how it would work with asyncio in python?
...point of fact, I am aware that multi-threaded asyncio doesn't happen out of the box in python and that it takes work to marshall a call into an executor. My point is that the moment of await is the synchronization point between the two threads.
Sorry, but I’m still kinda lost with regards your overall goals. One thing I can say, with reference to your most recent post, is that the concept of threads doesn’t really apply to Swift concurrency. There are tasks and actors, with the only edge case being that certain work, like UI state updates, have to be done on a specific actor.
Anyway, let’s start with some basics. Why are you not doing everything on the main actor? That’s my default position in all the code I write (in a pre Swift concurrency world it’s doing everything on the main thread). There are good reasons to move off the main actor — typically because you have something that’s either CPU or I/O bound  — but my default position is to do everything on the main actor and then move very specific stuff to secondary actors if they warrant it.
 Oh, and I mean synchronous I/O bound here, like walking a large file system hierarchy. If the underlying I/O abstraction supports async operations, like URLSession , it’s often fine to do all the bookkeeping on the main actor.
For the first part of this thread, the goal was just more efficient code. The assumption is that this:
is more efficient than this:
Negligibly so, sure. But I actually care about the efficiency of my code. It also makes for fewer edits in the migration to concurrency. If the former is not allowed in swift, so be it. Not a big deal. Was just curious and wanted to advocate.
For the second side note, the overall Task is run in a method of an ObservableObject, which serves as one of my view models and which is triggered by some user interaction with SwiftUI. The added complication is that the class inherits some functionality from NSObject and conforms to a protocol that is common to most of the models in my project. So:
And I agree that doing everything in the main actor is ideal, which is what I kind of assumed would be the case. And, if I'm not mistaken, I would be able to add the @MainActor modifier to the class definition to make it so... except for the inheritance. And the modifier prevented the class from conforming to the protocol.
The await someAsyncFunc(....) is ultimately async because it makes a URLSession dataTask call. At run time, when it returned, I got a purple warning/error about the code not running on the main actor, and the UI didn't update. As written above, it seems to work. But I posted just because I'm still new to the api and maybe there's a better way given the constraints of my situation.
And my critique, then, with my limited perspective, is that it should be main actor by default, and the declaration I would like to add would mark whatever can be pushed off to other actors: something like await method() on someClassOfActor . Arguably, each method in the call stack from someAsyncFunc down to just before the actual dataTask is quick enough to run on the main actor. And every step processing the returned data back up the call stack is also probably quick enough. So that, at least in my case, main actor by default without extra decorators would be sufficient.
PromiseKit, the package from which I'm migrating, essentially operates this way, dispatching each callback on the main queue by default unless instructed otherwise.
- Skip to main content
- Skip to primary sidebar
- Skip to footer
Super Fast Python
making you awesome at concurrency
How to Get Asyncio Task Results
November 11, 2022 by Jason Brownlee in Python Asyncio
Last Updated on November 14, 2023
A coroutine may return a result directly via a return value.
Asyncio tasks that execute a coroutine run asynchronously. Therefore we need a way to retrieve results from coroutines executed by independently run coroutines.
In this tutorial, you will discover how to get a result from an asyncio task .
After completing this tutorial, you will know:
- How to retrieve a result from an asyncio task.
- What happens to the result if the task fails or is cancelled.
- What happens if we try to retrieve a result from a running task.
Let’s get started.
Table of Contents
- What is an Asyncio Task
An asyncio Task is an object that schedules and independently runs an asyncio coroutine.
It provides a handle on a scheduled coroutine that an asyncio program can query and use to interact with the coroutine.
A Task is an object that manages an independently running coroutine. — PEP 3156 – Asynchronous IO Support Rebooted: the “asyncio” Module
An asyncio task is represented via an instance of the asyncio.Task class .
A task is created from a coroutine. It requires a coroutine object, wraps the coroutine, schedules it for execution, and provides ways to interact with it.
A task is executed independently. This means it is scheduled in the asyncio event loop and will execute regardless of what else happens in the coroutine that created it. This is different from executing a coroutine directly, where the caller must wait for it to complete.
Tasks are used to schedule coroutines concurrently. When a coroutine is wrapped into a Task with functions like asyncio.create_task() the coroutine is automatically scheduled to run soon — Coroutines and Tasks
We can create a task using the asyncio.create_task() function .
This function takes a coroutine instance and an optional name for the task and returns an asyncio.Task instance.
Wrap the coro coroutine into a Task and schedule its execution. Return the Task object. — Coroutines and Tasks
You can learn more about asyncio tasks in the tutorial:
Now that we know about asyncio tasks, let’s look at how we might get results from tasks.
Run your loops using all CPUs, download my FREE book to learn how.
How to Get Task Result
We can get the result of a task via the result() method .
This method returns the return value of the coroutine wrapped by the Task or None if the wrapped coroutine does not explicitly return a value.
If the coroutine fails with an unhandled exception, it is re-raised when calling the result() method and may need to be handled.
If the task was canceled, then a CancelledError exception is raised when calling the result() method and may need to be handled.
As such, it is a good idea to check if the task was canceled first.
If the task is not yet done, then an InvalidStateError exception is raised when calling the result() method and may need to be handled.
As such, it is a good idea to check if the task is done first.
Now that we know how to get the return value from a task, let’s look at some worked examples.
Confused by the asyncio module API? Download my FREE PDF cheat sheet
Example of Getting a Result From a Done Task
We can explore how to get a return value result from a successfully done task.
In this example, we define a task coroutine that reports a message, blocks for a moment, then returns a value.
We then define the main coroutine that is used as the entry point into the asyncio program. It reports a message, creates and schedules the task, then awaits the task to be completed. Once completed, it retrieves the result from the task and reports it.
The complete example is listed below.
Running the example starts the asyncio event loop and executes the main() coroutine.
The main() coroutine reports a message, then creates and schedules the task coroutine.
It then suspends and awaits the task to be completed.
The task runs, reports a message, and sleeps for a moment before returning a value and terminating normally.
The main() coroutine resumes and retrieves the return value result from the task, which is then reported.
This example highlights the normal case of retrieving a return value from a successful task.
Next, we will look at getting a result from a task that does not return a value.
Free Python Asyncio Course
Download my asyncio API cheat sheet and as a bonus you will get FREE access to my 7-day email course on asyncio.
Discover how to use the Python asyncio module including how to define, create, and run new coroutines and how to use non-blocking I/O.
Example of Getting a Result From a Task That Does Not Return a Value
If the coroutine that the task wraps does not return a value, then the result() method returns the default return value None.
We can explore getting a result from a task that does not return a value.
In the example below, we update the example from the previous section so that the task coroutine does not return a value.
The task runs, reports a message, and sleeps for a moment before terminating normally without returning a value.
The main() coroutine resumes and attempts to retrieve a return value result from the task, which is then reported.
We can see that the return value for the task is None because it did not explicitly return a value.
This example highlights that we can retrieve a result from a task that does not return a value and expect to receive the value None.
Overwhelmed by the python concurrency APIs? Find relief, download my FREE Python Concurrency Mind Maps
Example of Getting a Result From a Failed Task
If the task fails with an unhandled exception, the exception will be re-raised when calling the result() method on the task to get the result.
As such, we may need to handle possible exceptions when getting task results.
We can explore getting a result from a task that failed with an unhandled exception.
In this example, we can update the task coroutine to explicitly raise an exception that is not handled.
This will cause the task’s coroutine to fail.
The main coroutine will sleep to wait for the task to be completed. This is to avoid using the await expression which will also propagate the exception back to the caller.
You can learn more about handling exceptions in tasks in the tutorial:
- How to Handle Asyncio Task Exceptions
Once the task is done, the main coroutine will attempt to retrieve the return value and handle the exception that is re-raised.
The task runs, reports a message, and sleeps for a moment. The task resumes and raises an exception.
The exception does not terminate the application or the asyncio event loop.
Instead, the exception is captured by the asyncio event loop and stored in the task.
The main() coroutine resumes and then attempts to retrieve the result from the task. This fails and the exception that was raised and not handled in the Task ‘s wrapped coroutine is re-raised in the caller.
The main() coroutine catches the exception and reports its details.
Next, let’s look at what happens if we try to get a task result from a running task.
Loving The Tutorials?
Why not take the next step? Get the book.
Example of Getting a Result From a Running Task
We cannot retrieve results from a running asyncio task.
Instead, we can only retrieve a result from a task after it is done.
If we call the result() method on a task that is scheduled or running, an InvalidStateError exception is raised by the caller.
In the example below, we update the above example so that the main coroutine schedules the task, waits a moment then attempts to get the result too soon before the task has been completed.
This is expected to raise an InvalidStateError .
It then suspends and sleeps for a moment.
The task runs, reports a message, and sleeps for a moment.
The main() coroutine resumes and attempts to retrieve the result from the task while the task is running, even though the task is suspended.
This fails with an InvalidStateError that breaks the asyncio event loop in this case.
This example highlights that we must always retrieve a Task result after the task is done.
We can check if a task is done before retrieving the exception via the done() method that will return True if the task is done, or False otherwise.
Next, we can look at the case of attempting to get a task result from a canceled task.
Example of Getting a Result From a Canceled Task
We cannot retrieve a result from a canceled task.
Although a canceled task is done, a result will not be available and cannot be retrieved.
Instead, a CancelledError exception is raised when calling the result() method if the task was canceled.
The example below updates the previous example to create and schedule the task as before, then wait a moment. It then cancels the task, waits a moment for the task to be canceled, then attempts to get the result.
This is expected to fail as the result() method will re-raise the CancelledError exception from the wrapped coroutine that was used to cancel the task.
The main() coroutine resumes and cancels the task. It then suspends and waits a moment for the task to respond to the request for being canceled.
The task is canceled by raising a CancelledError within the wrapped coroutine.
The main() coroutine resumes and attempts to retrieve the result.
This fails and the CancelledError exception is re-raised in the caller.
This breaks the event loop in this case.
This section provides additional resources that you may find helpful.
- Python Asyncio Jump-Start , Jason Brownlee, 2022. ( my book! )
- Python Asyncio Interview Questions , Jason Brownlee, 2022.
- Asyncio Module API Cheat Sheet
I also recommend the following books:
- Python Concurrency with asyncio , Matthew Fowler, 2022.
- Using Asyncio in Python , Caleb Hattingh, 2020.
- Python Asyncio: The Complete Guide
- Python Asynchronous Programming
- asyncio — Asynchronous I/O
- Asyncio Coroutines and Tasks
- Asyncio Streams
- Asyncio Subprocesses
- Asyncio Queues
- Asyncio Synchronization Primitives
- Asynchronous I/O, Wikipedia .
- Coroutine, Wikipedia .
You now know how to get a result from an asyncio task in Python.
Do you have any questions? Ask your questions in the comments below and I will do my best to answer.
Photo by Hunter Newton on Unsplash
About Jason Brownlee
Hi, my name is Jason Brownlee, Ph.D. and I’m the guy behind this website. I am obsessed with Python Concurrency.
I help python developers learn concurrency, super fast. Learn more .
Do you have any questions cancel reply, learn asyncio fast (without the frustration).
What if you could develop Python programs that were asynchronous from the start?
The asyncio module provides easy-to-use coroutine-based concurrency for asynchronous programming.
Introducing: " Python Asyncio Jump-Start ".
A new book designed to teach you the asyncio module step-by-step, super fast!
Enter the destination URL
Or link to existing content