Gillardo
Gillardo

Reputation: 9818

When are async and await needed in c#?

Exactly as the title says. I wonder if i am writing async and await when it is not needed.

I have seen methods like this with the async tag

public async Task CreateAsync(User user)
{
    if (_context.Entry<User>(user).State == EntityState.Detached)
    {
        _context.Set<User>().Add(user);
    }

    _context.Entry<User>(user).State = EntityState.Added;

    await _context.SaveChangesAsync();
}

and like this without it

public Task CreateAsync(User user)
{
    if (_context.Entry<User>(user).State == EntityState.Detached)
    {
        _context.Set<User>().Add(user);
    }

    _context.Entry<User>(user).State = EntityState.Added;

    return _context.SaveChangesAsync();
}

Both compile fine. I am always adding the async and await keywords and wonder maybe if i am doing it wrong and writing them when they are not needed?

EDIT:

If you are actually returning a value, should this be written using the async/await keywords, or without. Here is a version with the keywords

public async Task<User> CreateAsync(User user)
{
    if (_context.Entry<User>(user).State == EntityState.Detached)
    {
        _context.Set<User>().Add(user);
    }

    _context.Entry<User>(user).State = EntityState.Added;

    await _context.SaveChangesAsync();

    return user;
}

here is another example

public Task<User> FindByIdAsync(long userId)
{
    return _context.Users.FindAsync(userId);
}

public async Task<User> FindByIdAsync(long userId)
{
    return await _context.Users.FindAsync(userId);
}

EDIT 2

Excellent answers so far, but 1 last example. Since i have my async calls, how would i cope with calling multiple async functions from 1 method. here is an example of what i have, but i dont know if it is right. I do not want the method to exit until all AddAsync methods are completed. Is this correct

private async Task AddPermissions(DataContext context)
{
    var permissionService = new PermissionService(context);

    await permissionService.AddAsync(new Permission("CanView", "View company"));
    await permissionService.AddAsync(new Permission("CanAdd", "Add and view company"));
    await permissionService.AddAsync(new Permission("CanEdit", "Edit and view company"));
    await permissionService.AddAsync(new Permission("CanDelete", "Delete and view company record"));

    await permissionService.AddAsync(new Permission("CanAdd", "Add new pages"));
    await permissionService.AddAsync(new Permission("CanEdite", "Edit existing pages"));
    await permissionService.AddAsync(new Permission("CanDelete", "Delete a page"));

    await permissionService.AddAsync(new Permission("CanAdd", "Add new page content"));
    await permissionService.AddAsync(new Permission("CanEdit", "Edit existing page content"));
    await permissionService.AddAsync(new Permission("CanDelete", "Delete page content"));
}

Upvotes: 3

Views: 905

Answers (4)

Thunder
Thunder

Reputation: 10986

A simpler way would be to call Task.Factory.StartNew

Task.Factory.StartNew(() => new Permission("CanView", "View company"));
Task.Factory.StartNew(() => new Permission("CanAdd", "Add and view company"));
...

Upvotes: 0

NeddySpaghetti
NeddySpaghetti

Reputation: 13495

IMHO you would only need to use the await if you want the SaveChangesAsync operation to have finished when the method returns or you need to do something with the result of the async operation. In this case you are not doing anything with it so it is better to not have an async method and avoid the generation of the state machine withing the method, resulting in more efficient code.

Regarding your second edit, you are correct. Although the method will return as soon as the first await is encountered all other awaited statements will be executed one after the other on the thread pool and then the result of the task will be updated. So if you await AddPermissions that statement will only complete once all internal permissionService.AddAsync calls are complete, unless an exception is thrown.

It is also possible to execute permissionService.AddAsync calls in parallel if necessary, by storing the returned tasks in a list and then awaiting Task.WhenAll

private async Task AddPermissions(DataContext context)
{
    var permissionService = new PermissionService(context);

    List<Task> permissionRequests = new List<Task>();

    permissionRequests.Add(permissionService.AddAsync(new Permission("CanView", "View company")));
    permissionRequests.Add(permissionService.AddAsync(new Permission("CanAdd", "Add and view company")));
    permissionRequests.Add(permissionService.AddAsync(new Permission("CanEdit", "Edit and view company")));
    permissionRequests.Add(permissionService.AddAsync(new Permission("CanDelete", "Delete and view company record")));

    permissionRequests.Add(permissionService.AddAsync(new Permission("CanAdd", "Add new pages")));
    permissionRequests.Add(permissionService.AddAsync(new Permission("CanEdite", "Edit existing pages")));
    permissionRequests.Add(permissionService.AddAsync(new Permission("CanDelete", "Delete a page")));

    permissionRequests.Add(permissionService.AddAsync(new Permission("CanAdd", "Add new page content")));
    permissionRequests.Add(permissionService.AddAsync(new Permission("CanEdit", "Edit existing page content")));
    permissionRequests.Add(permissionService.AddAsync(new Permission("CanDelete", "Delete page content")));

    await Task.WhenAll(permissionRequests);
}

So each call to permissionService.AddAsync kicks off the request and adds the corresponding task to the list. Once you have kicked off all the requests, you can await all their completion with await Task.WhenAll, this will wait until they are all complete or have returned an error. Any exceptions thrown will be stored in the task returned from Task.WhenAll. Awaiting that task will rethrow the first exception, but you can access all of them using the Task.Excpetion property which contains an AggregatedException which in turn contains all the thrown exceptions.

Upvotes: 3

Vikas Gupta
Vikas Gupta

Reputation: 4465

The topic can be deep, but here is a high level overview.

There is a key difference between the two versions of code you have posted above.. But first the important similarity..

They both tell _context to save changes asynchronously.. Because that's how SaveChangesAsync is implemented.


Now, the difference..

In first version, when you use async and await keyword, any code which may be after the await call (in this case, the await call is the last call in the function), compiler turns it into a continuation, and it is supposed to execute after the await call finishes (asynchronously). This includes any exception handling which might wrap the async call.

In contrast, in the second version, when we take the return value of the async call as a Task, then while the async operation has been kicked off, and yet to finish, the execution will continue on in that method (It is not turned into a continuation by the compiler). No code is set to execute after the async operation finishes (unless you explicitly use .ContinueWith on the Task object).


Why would you use one over the other?

Again, at high level async and await should would fine for normal scenarios, where you want to advantage of compiler doing some magic for you, in order to make it easier to deal with async calls.. However, it is also less flexible for some scenarios. Example - What if you want to kick off 10 ping operations asynchronously, and then write a continuation when all 10 finish. The same is not possible using async and await keywords for each async ping. (First call to await makes rest of the code a continuation to first async call).


Search more on internet for more details on async await.. It can be pretty deep, but worth understanding the details.

Upvotes: 0

Jonathan Allen
Jonathan Allen

Reputation: 70287

The second version is slightly more efficient, but it doesn't allow you to go back and add more code later without rewriting it to use async/await.

That's pretty much it. I wouldn't complain if I saw either pattern in my code base.

Upvotes: 0

Related Questions