Developers often use async methods in C# Asynchronous Programming to improve the throughput of applications. In this article, we will learn about the usage of three different statements in C# async methods. When working with asynchronous programming in C#, developers may encounter these constructs that serve distinct purposes. Understanding their use case is vital to writing clear and efficient asynchronous code.
Let’s start.
Using Task.CompletedTask in Async Methods
Assuming we want to adhere to the asynchronous programming pattern. Let’s consider a method that only returns a task without actually performing any asynchronous work.Â
In this case, we can use Task.CompletedTask
. It’s similar to saying, “We have completed the task, and here’s a task to indicate the completion.”
Let’s create the TaskCompletedHandler
class to understand this:
public class TaskCompletedHandler { public Task UseTaskCompletedAsync() { Console.WriteLine("Not performing any asynchronous work."); return Task.CompletedTask; } }
By using Task.CompletedTask
in the UseTaskCompletedAsync()
method, we avoid unnecessary overhead and resource consumption, as we’re only writing to the console and returning an already completed task without additional processing.
Although Task.CompletedTask
is often used in asynchronous methods, it can be useful in synchronous methods as well to maintain consistency in method signatures, especially when we have both asynchronous and synchronous versions of a method.
Let’s see what a synchronous counterpart of our method looks like:
public void UseTaskCompletedSync() { Console.WriteLine("Not performing any asynchronous work."); Task.CompletedTask.Wait(); }
We are able to ensure consistency in the UseTaskCompletedSync()
method by using a void
return type and performing synchronous work as well.
We use Task.CompletedTask.Wait()
in our method, however, using it in a synchronous method isn’t a recommended practice, as it can lead to blocking the current thread, defeating the purpose of asynchronous programming.
Recognizing that blocking threads in synchronous contexts can lead to real-world performance and scalability issues is critical.
Using Task.FromResult in Async Methods
The Task.FromResult
is a sibling to Task.CompletedTask
. It is a useful method in async programming to create a completed Task
with a specific result. It also allows us to quickly return a completed Task
in situations where we don’t have asynchronous operations.
Let’s create the UseTaskFromResultAsync()
method to understand this:
public async Task<string> UseTaskFromResultAsync() { Console.WriteLine("Not performing any asynchronous work but returning a result."); var message = "Hello, world!"; return await Task.FromResult(message); }
Using Task.FromResult
in the UseTaskFromResultAsync()
method allows us to return a string
result with the Task
. Additionally, we use Task<string>
in our method signature rather than Task
.
In synchronous methods, we could also potentially use Task.FromResult
, however, its usage might not align with the typical intention of synchronous methods:
public string UseTaskFromResultSync() { Console.WriteLine("Not performing any asynchronous work but returning a result."); var message = "Hello, world!"; return Task.FromResult(message).Result; }
When we remove the async
and Task
keywords that we had in the last code block, we need to remove the await keyword also so that we don’t get a compiler error.
Here we need to get the result value of our task by using the Result
property as we did in the UseTaskFromResultSync()
method. In this method, we use Task.FromResult
to return a completed Task<string>
. Note that using Result
is not advised for the same reasons as the Wait()
method.Â
Using Return Statements in Async Methods
On the other hand, if our asynchronous method does perform actual asynchronous work, we’ll use the return statement. This will signify an ongoing asynchronous operation. Therefore, the caller is notified that we are working asynchronously, with a task representing progress and completion.
Let’s create the UseReturnAsync()
method and see how we can achieve this using a return
statement:
public class ReturnHandler { public async Task<int> UseReturnAsync() { Console.WriteLine("About to perform some asynchronous work."); await Task.Delay(1000); return 20; } }
In the UseReturnAsync()
method, we write a message to the console, carry out an asynchronous operation, and then return a task representing the completion of that operation. The caller can await this task to wait for the result.
When using the return
statement in an asynchronous method, the method execution is temporarily paused until the awaited operation completes.
The return
keyword is used in a similar way in both synchronous and asynchronous methods to provide a value as the result of the method. Whether the method is synchronous or asynchronous, the basic purpose of the return
keyword remains the same – to send a value back from the method to the calling code.
Conclusion
In this article, we learned the usage of Task.CompletedTask
,Task.FromResult
and return
in C# asynchronous methods. We also delved a little into their usage in a synchronous method.
In a nutshell, developers employ Task.CompletedTask
when our method doesn’t perform actual asynchronous work but still needs to provide a completed task. We use Task.FromResult
in a manner similar to Task.CompletedTask
but include a result.
On the other hand, the return
statement is used when our method performs asynchronous operations and returns a task representing their completion.
By grasping the usage and distinction between these constructs, we can write concise and efficient asynchronous codes.