0

I am writing a blazor server web application.

This application works with a database and Entity Framework.

Here is a method i've wrote:

private void MyMethod()
{
    var query = mydbcontext.mytable1.Where(t => ...);
    foreach (var item in query)
    {
       ...
    }
}

As you can see this method is not declared with "async Task". So she will be called without "await" keyword.

I can declare it with "async Task" and call it with "await". It works but it gives me a warning because i have no async call inside.

Let's suppose i decide to not declare it with "async Task" in order to avoid the warning.

What will happen if i need to change something in my function later which needs an Async call (For example this):

private async Task MyMethod()
{
    var query = mydbcontext.mytable1.Where(t => ...);
    foreach (var item in query)
    {
       ...
    }
    var countresult = await query.CountAsync();
}

I will need to search all calls of MyMethod and add "await" on each of this calls.

To prevent that, I am wondering if i should not declare all my methods with "async Task". But this is ugly because i will get warnings.

What is the best practice ?

And is there a way to do this loop as ASync ?

private async Task MyMethod()
{
    var query = mydbcontext.mytable1.Where(t => ...);
    await foreachAsync (var item in query)
    {
       ...
    }
    var countresult = await query.CountAsync();
}

Thanks

6
  • What will happen if i need to change something in my function later which needs an Async call (For example this) - then you change your method to async. other options: 1) ignore the warning or 2) make it just a Task (not async), if you want it to already be awaitable. Commented Feb 18, 2022 at 20:43
  • "async-ness" is always determined by the end point of the method. Meaning you don't build up async methods from the front back, but if something from the back requires async then you work that in from the back to the front, if that makes sense. There is some overhead to making your method async, the compiler essentially builds a whole state machine around your method. You are getting those warnings for a reason. Commented Feb 18, 2022 at 20:49
  • 1
    Also you may actually want to be doing a await mydbcontext.mytable1.Where(t => ...).ToListAsync() anyways. because the foreach may be doing an internal non-async ToList but I don't know this for a fact. Commented Feb 18, 2022 at 20:51
  • To answer your second question, there is an await foreachin C# 8 but it requires an IAsyncEnumerable instead of the normal IEnumerable, which I am not sure if IQueryables use yet. learn.microsoft.com/en-us/archive/msdn-magazine/2019/november/… Commented Feb 18, 2022 at 20:53
  • OT when you end your query definition with .Where(), you are not executing it yet. That happens when you foreach over it. And again when you do a CountAsync (or plain Count) Commented Feb 18, 2022 at 21:01

2 Answers 2

6

... I am wondering if i should not declare all my methods with "async Task".

If there is no I/O being performed by the method then there is no need to make the method return a Task or Task<T> and by extension no need to use the async keyword.

If the method does do operations that use I/O (like a database call) then you have a couple of options and what you choose depends on the context your code is being used in.

When to use Asynchronous Operations

If the context can benefit from using asynchronous I/O operations, like the context of a web application, then your methods should return Task or Task<T>. The benefit of using asynchronous I/O is that threads are not locked waiting on I/O to complete which is important in applications that need to service many simultaneous requests that are thread resource intensive (like a web application). A windows forms or wpf application is also applicable because it allows for easy coding so the main thread can resume operations while I/O completes (the ui won't appear frozen).

When not to use Asynchronous Operations

If the context cannot benefit from using asynchronous I/O operations then make all your methods synchronous and do not return Task or Task<T>. An example of this would be a console application or a windows service application. You mentioned a Blazor web application in your question so this scenario does not appear to apply to you.

Provide both Asynchronous Operations and Synchronous Operations

If you want to allow the caller to choose then you can include 2 variations for each call that executes an I/O operation. A good example of this are the extensions written for Entity Framework. Almost all of the operations can be performed asynchronously or synchronously. Examples include Single/SingleAsync, All/AllAsync, ToArray/ToArrayAsync, etc.

This approach will also allow you to extend a public method later by adding an async overload if you extend the operation to include an I/O call at a future point in time. There are ways to do this without creating too much code duplication. See Async Programming - Brownfield Async Development by Stephen Cleary for various techniques. My favorite approach is the The Flag Argument Hack.


Side note: If you do not need to consume the results of an I/O call in your method and there is only one I/O method you do not need to use the async/await keywords. You can return the result of the I/O operation directly.

Sign up to request clarification or add additional context in comments.

Comments

0
private void MyMethod()
{
    var query = mydbcontext.mytable1.Where(t => ...);
    foreach (var item in query) // Here you will retrieve your data from the DB.
    {
       ...
    }
    
    // If you're going to count it here, don't use the query. You already retrieved 
    // the data for your foreach.
}

It would be better to retrieve the data before the foreach. That way you will not call the DB twice for 1. the foreach, 2. the count. I.E.

private async Task MyMethod()
{
    var data = await mydbcontext.mytable1.Where(t => ...).ToListAsync();
    foreach (var item in data)
    {
       ...
    }

    // Don't call the DB here. You already have the data, so just count the items in 
    // the list.
    var countresult = await data.Count; 
}

6 Comments

This is a good approach for OP, but this doesn't actually answer his question
This is a bad approach for OP (because it loads all data in memory at the same time), but this doesn't actually answer his question. (just to post opposite opinion to @maksymiuk comment, attribution: some text of this comment is based on maksymiuk's comment).
@AlexeiLevenkov does the foreach not load all the data into memory?
@maksymiuk foreach will load items one by one (normally) so unless code simply adds result to some collection only one item from the result is in memory at one time . I.e. you can collect some statistical data about all records using without loading terrabytes of results in memory all at once. Indeed usually one would make filtering part of the query and just show all results anyway so code is slarge equivalent but for large results the difference may be significant.
@maksymiuk (I'm not 100% sure if EF actually does lazy loading by default... I mostly wrote the comment to just be opposite of what you said :). Whether foreach loads items all at the same time of on-by-one depends on how underlying provider implements iteration/MoveNext)
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.