0

Imagine you have a variable databaseContext and want to select all users from the users table. From the docs

https://learn.microsoft.com/en-us/ef/core/querying/

you might create this method:

public IEnumerable<User> GetUsers() => _databaseContext.Users;

but isn't that synchronous code? And I think this is completely wrong:

public Task<User[]> GetUsers() => _databaseContext.Users.ToArrayAsync();

I would expect a method like this:

public Task<IEnumerable<User>> GetUsers() => _databaseContext.Users /* but select them asynchronously */;

Since EF Core provides synchronous and asynchronous methods e.g. Find and FindAsync I don't know how to query all data asynchronously. Maybe EF Core does this under the hood somehow but then I should have to await it, right?

Question3r
  • 2,166
  • 19
  • 100
  • 200
  • Your `Task` examples would return the entire list when you await it. Are you looking for something like `.AsAsyncEnumerable()` ... `await foreach(...)` to process records as they are returned? – Jeremy Lakeman Feb 14 '21 at 14:05
  • Currently I have no usecase for it yet. This method should just query all the users from the database asynchronously. I think simply using `_databaseContext.Users` would run synchronously so it would freeze the application, no? – Question3r Feb 14 '21 at 14:12
  • Users in your case is an IQueryable, and it's not realized (synchronously or asynchronously) until you call a method like ToList or foreach over it, at which point the expressions behind it are converted to sql and executed. Now, it will depend on the method used when you realize it. ToList vs ToListAsync or AsAsyncEnumerable etc. Referring to Users on its own doesn't do much of anything (sync nor async) – pinkfloydx33 Feb 14 '21 at 14:21
  • ah ok so `AsAsyncEnumerable` does not return a `Task>` so I think there is no other way to change the return type to `Task>` and call this `_databaseContext.Users.ToListAsync()`? – Question3r Feb 14 '21 at 14:27

1 Answers1

2
  1. The following code will give you the list of all users asynchronously -
public async Task<IEnumerable<User>> GetUsers() => await _databaseContext.Users.ToListAsync();

Here the async keyword identifies the method as an asynchronous method (so that you can call it accordingly) and the await keyword makes sure - i) the calling thread isn't blocked and, ii) the result is awaited. Which thread (the calling one itself or a separate one) receives the awaited result later depends on how the Task Library handles async/await operations under-the-hood. And that exactly is what the Task Library is intended to do - taking away the concept of thread from the developers' hand forcing them to think in terms of Task, a higher level abstraction it provides instead.

For more -
Dissecting the async methods in C#
https://stackoverflow.com/a/42291461/446519

  1. This -
public IEnumerable<User> GetUsers() => _databaseContext.Users;

is a synchronous method, but more importantly it doesn't query the database. To query the database and then return the list of all users (synchronously) call ToList() -

public IEnumerable<User> GetUsers() => _databaseContext.Users.Tolist();
  1. This -
public Task<User[]> GetUsers() => _databaseContext.Users.ToArrayAsync();

is also a synchronous method and it will return a Task<User[]> instance immediately. But you can use the returned task object later, to get the result asynchronously, like -

Task<User[]> task = this.GetUsers();
User[] users = task.GetAwaiter().GetResult();

EDIT :
In point No.3 above, the code used after returning the Task<User[]> is purely to demonstrate the fact - "its your Task object now and you can use it later to get the result from it asynchronously". Showing what approach you should or shouldn't use for doing that was not the intention.

As @pinkfloydx33 suggested in the comment, .GetAwaiter().GetResult() might not always be the best approach to get the result. You can do the same without being explicit about getting the awaiter as -

Task<User[]> myTask = this.GetUsers();
User[] users = await myTask;
atiyar
  • 7,762
  • 6
  • 34
  • 75
  • thanks for that. Just wondering why `public async Task> GetUsers() => await _databaseContext.Users.ToListAsync();` is not the same as `public Task> GetUsers() => _databaseContext.Users.ToListAsync();` since the second approach should return a pending task too no? Coming from JavaScript this would be fine, since it's a Promise but it seems in C# I still have to await it. – Question3r Feb 14 '21 at 15:52
  • Because `Task>` is **not** a `Task>` since classes are *invariant* However, `async`+`await` provides some special magic whereby you can "think" of the method returning a `List` which *is* an `IEnumerable` – pinkfloydx33 Feb 14 '21 at 15:58
  • I wouldn't reccomend the use of GetAwaiter+GetResult unless the dev absolutely knew what they were doing (ie. their name is likely Stephen). You can `await` the stored task instead – pinkfloydx33 Feb 14 '21 at 16:01
  • "but you can execute the returned task later" is incorrect. What you have there is a hot task--it's already started. You can however [a]wait for it to complete – pinkfloydx33 Feb 14 '21 at 16:04
  • @Question3r Even though your second code will not compile, I think you didn't mean that. To get what you are trying to understand, you'll need a bit idea of how the Task library handles `async/await` operations under-the-hood. Simply put, when you `await`, you will not be blocking the current thread (hence your UI is not frozen) and you will get a `List` _asynchronously_, but if you do not `await` you are returned the `Task>` directly. Its up to you what you want to do with that. You might want to check this answer - https://stackoverflow.com/a/42291461/446519 – atiyar Feb 14 '21 at 17:01
  • @pinkfloydx33 I'm not sure is its already started. If you check the `Status` of the task, it says `WaitingForActivation`, and I'm sure the query won't be sent to the database unless I call the `GetResult()`. And of course, I could `await` the stored task instead `GetAwaiter+GetResult`, but my intention was imply the OP that "its your `Task` now, its up to you how you get the result out of it" without being purist. But thanks a lot for your suggestion, I'll add an Edit :) – atiyar Feb 14 '21 at 17:18
  • Javascript promises run on the main (and only) thread by queuing an event when they complete. Once a promise is complete, calling `.then` again would fire an event immediately. Task's in dotnet core are similar, except that the runtime is multi-threaded and tasks will resume on a thread pool. If a task is already complete when you await, execution continues immediately. await is not required to continue execution, but is required to observe the result. – Jeremy Lakeman Feb 14 '21 at 23:20