Reputation: 1047
In a C# console app, I have a repo class with a couple of async methods:
public class SomeRepo
{
internal Task<IList<Foo>> GetAllFooAsync()
{
// this is actually fake-async due to legacy code.
var result = SomeSyncMethod();
return Task.FromResult(result);
}
public Task<IList<Foo>> GetFilteredFooAsync()
{
var allFoos = await GetAllFooAsync().ConfigureAwait(false);
return allFoos.Where(x => x.IsFiltered);
}
}
In Program.cs
:
var someRepo = new SomeRepo();
var filteredFoos = someRepo.GetFilteredFooAsync(); // no await
// a couple of additional async calls (to other classes) without await..
// .. followed by:
await Task.WhenAll(filteredFoos, otherTask, anotherTask).ConfigureAwait(false);
What is baffling me is that if I place a break point on the 2nd line in Program.cs
, the call to someRepo.GetFilteredFooAsync()
does not proceed to the next line, but instead is stuck until the operation is complete (as though it was synchronous). Whereas if I change the call to GetAllFooAsync
(in GetFilteredFooAsync
) to be wrapped within a Task.Run
:
public class SomeRepo
{
internal Task<IList<Foo>> GetAllFooAsync() { // ... }
public Task<IList<Foo>> GetFilteredFooAsync()
{
var allFoos = await Task.Run(() => GetAllFooAsync).ConfigureAwait(false);
return allFoos.Where(x => x.IsFiltered);
}
}
..the operation works as expected this way. Is it because GetAllFooAsync
is actually synchronous, but imitating an asynchronous workflow?
EDIT: Reworded the title and added the internals of GetAllFooAsync
as I've realized they could be the culprit of the issue.
Upvotes: 3
Views: 2171
Reputation: 456437
You've already got some good answers that describe how async
doesn't make things asynchronous, how async
methods begin executing synchronously, and how await
can act synchronously if its task is already completed.
So, let's talk about the design.
internal Task<IList<Foo>> GetAllFooAsync()
{
// this is actually fake-async due to legacy code.
var result = SomeSyncMethod();
return Task.FromResult(result);
}
As noted, this is a synchronous method, but it has an asynchronous signature. This is confusing; if the method is not asynchronous, it would be better with a synchronous API:
internal IList<Foo> GetAllFoo()
{
// this is actually fake-async due to legacy code.
var result = SomeSyncMethod();
return result;
}
and similar for the method that calls it:
public IList<Foo> GetFilteredFoo()
{
var allFoos = GetAllFoo();
return allFoos.Where(x => x.IsFiltered);
}
So now we have synchronous implementations with synchronous APIs. The question now is about consumption. If you are consuming this from ASP.NET, I recommend consuming them synchronously. However, if you are consuming them from a GUI application, then you can use Task.Run
to offload the synchronous work to a thread pool thread, and then treat it as though it were asynchronous:
var someRepo = new SomeRepo();
var filteredFoos = Task.Run(() => someRepo.GetFilteredFoo()); // no await
// a couple of additional async calls (to other classes) without await..
// .. followed by:
await Task.WhenAll(filteredFoos, otherTask, anotherTask).ConfigureAwait(false);
Specifically, I do not recommend using Task.Run
to implement, e.g., GetAllFooAsync
. You should use Task.Run
to call methods, not to implement them, and Task.Run
should mostly be used to call synchronous code from a GUI thread (i.e., not on ASP.NET).
Upvotes: 4
Reputation: 1994
Presence of async
keyword doesn't make a method asynchronous it just signals compiler to make conversion the code of the method into state machine class which is ready to be used with asynchronous flows. Effectively a method becomes asynchronous when it does an asynchronous operation such as I/O, offloading work to another thread, etc., and in that case it consists of 3 parts: the synchronous part which precedes an asynchronous operation, call of asynchronous operation which initiates the operation and returns control to calling thread, and a continuation. In your case the last two parts are absent so your call is synchronous.
Upvotes: 3
Reputation: 11514
Is it because GetAllFooAsync is actually synchronous, but imitating an asynchronous workflow?
Yes, Task.FromResult
returns a task that is immediately RanToCompletion
so it is synchronous. People often forget that Tasks may in some cases already be complete when they are returned and therefore do not run asynchronously.
Upvotes: 2