I am just curious about a scenario around returning a `Task` vs `void` from a synchronous function. Given I have a function, as below, that can either return a Task or a void. I am just curious if there is any benefit in returning a `Task` rather than just a `void`?
public void SynchronousFunction(/* some input */) { /* some synchronous operation */}
// VS
public Task SynchronousFunction(/* some input */)
{
/* some synchronous operation */
return Task.CompletedTask
}
// VS Variation of Task return function
public Task SynchronousFunction(/* some input */)
{
return Task.Run(() => /* some synchronous operation */
}
My view is two fold. Firstly, it feels like returning a Task would require some unnecessary boxing and unboxing, and, secondly, if the function gets called in some synchronous code it doesn't need to be called with .Wait() or such like thing.
CodePudding user response:
In the first example your code executes synchronously. The calling thread calling the method is blocked, waiting for the method execution to complete before doing anything else.
In some cases this is fine (e.g. some scripting procedure you need to run in order to maintain a database), some other times this is an issue (e.g. server side code inside an ASP.NET core application in the controller code).
The second example is basically the same, but you are instead returning a completed task. The code still completes synchronously, there is no difference. The caller gets back an already completed task object. Any form of waiting on that Task (both synchronous wait with the Wait()
method or asynchronous wait with the async
keyword) completes immediately, because the Task
object is already completed.
There is no boxing behavior involved, because Task
is a reference type. There is instead allocation, because you are creating an instance of a reference type (check out the docs for Task.CompletedTask
. They clearly say that calling it multiple times is not guaranteed to always return the same instance, in other words it is not a cached instance).
Notice that this method signature is somewhat confusing for the method caller: it may think that this method is doing I/O work, but it is actually doing CPU bound work. You should clearly document this behavior or even better avoid this signature at all if you can.
In the third example you are asking the thread pool to do some work, and you are returning a Task
object representing the ongoing operation that you have offloaded to the thread pool. The Task
object is basically a proxy that you can use to wait for the completion of the workload you have assigned to the thread pool.
When the method returns you simply know that you have asked the thread pool to do something, when and how this work is done is totally under the control of the runtime. Calling code typically waits for the task to be completed. This can be done synchronously, by blocking the thread (e.g. Wait()
) or asynchronously without blocking the thread (e.g. await
)
CodePudding user response:
Returning void with async should be avoided cause outside of the method you will not catch any exceptions happening in async void method.
CodePudding user response:
It depends on the usage, if you have to call some async operations in it like making request to an API or DB you can eighter have async Task
or async void
. Usually async just allows the tasks in the bottom of your call to be awaited nothing changes in the execution of your methods.
public Task SynchronousFunctionAsync(/* some input */)
{
return Task.CompletedTask;
// This method allows you to start new Task if you need
}
public async Task SynchronousFunctionAsync(/* some input */)
{
await Task.CompletedTask;
// This method allows you to await some other operations
}
public async void SynchronousFunction(/* some input */)
{
await Task.CompletedTask;
// This method also allows you to await some other operations
// but by calling it can't be awaited.
}