简体   繁体   中英

What happens when we don't await an async method

I have a .NET CORE 2 backend. In one of my controller endpoints, I'm creating invitations to be sent out via email. This seems to be a huge bottleneck on the endpoint and after thinking about it, I don't really need to wait for these invitations. If the email fails to send out, I can't really do anything about it anyway.

If I don't do await sendFn() would it essentially be a fire and forget method? I was reading on another stackoverflow thread that I'd have to do sendFn().ContinueWith(t => throw(t)) to be able to catch the exception since it'll be in another thread.

I have similar mailing functions around the code base. They each do slightly different things, but is there a service fn I can do to wrap these to make them fire and forget? I think some places I can just not use await (if that works), but some things alter the database context so if I don't await them I can potentially run into a case where something is accessing the same db context.

[HttpPost]
public async Task<IActionResult> CreateEvent([FromBody] Event val)
{
    _ctx.Event.Add(val);
    await _ctx.SaveChangesAsync();

    await SendInvitations(val); // fn in question

    return Ok();
}

public async Task SendInvitation(Event event)
{
   forEach (var person in event.people)
   {
     await _ctx.Invitation.Add(person); // This shouldn't happen while another iteration or some other async code elsewhere is using the db ctx.
     _ctx.SaveChangesAsync();
     await _mailService.SendMail(person.email,"you have been invited"); // don't really need to await this.

   }
}

I'm posting to my server with data about an event. After I create and save the event to the database, I go and create invitations for each person. These invitations are also database items. I then send out an email. I'm mostly worried that if I drop the await, then when I'm creating invitations, it may conflict with db context elsewhere or the next iteration.

To get your code to compile and run I had to make these changes:

public async Task<IActionResult> CreateEvent(Event val)
{
    _ctx.Event.Add(val);
    await _ctx.SaveChangesAsync();
    await SendInvitation(val);
    return Ok();
}

public async Task SendInvitation(Event @event)
{
    foreach (var person in @event.people)
    {
        await _ctx.Invitation.Add(person);
        await _ctx.SaveChangesAsync();
        await _mailService.SendMail(person.email, "you have been invited");
    }
}

I also had to write this harness code:

public OK Ok() => new OK();

public class Event
{
    public List<Person> people = new List<Person>();
}

public class Person
{
    public string email;
}

public interface IActionResult { }

public class OK : IActionResult { }

public class Invitation
{
    public Task Add(Person person) => Task.Run(() => { });
}

public static class _ctx
{
    public static List<Event> Event = new List<Event>();
    public static Invitation Invitation = new Invitation();
    public static Task SaveChangesAsync() { return Task.Run(() => { }); }
}

public static class _mailService
{
    public static Task SendMail(string email, string message) { return Task.Run(() => { }); }
}

Then I updated SendInvitation like this:

public async Task SendInvitation(Event @event)
{
    Thread.Sleep(2000);
    foreach (var person in @event.people)
    {
        await _ctx.Invitation.Add(person);
        await _ctx.SaveChangesAsync();
        await _mailService.SendMail(person.email, "you have been invited");
    }
    Console.WriteLine("Done `SendInvitation`.");
}

Now, I can run it all like so:

var e = new Event();
e.people.Add(new Person() { email = "foo@bar.com" });
CreateEvent(e).ContinueWith(t => Console.WriteLine("Done `CreateEvent`."));
Console.WriteLine("Done `Main`.");

That outputs:

Done `Main`.

Then 2 seconds later:

Done `SendInvitation`.
Done `CreateEvent`.

If I simply change CreateEvent to this:

public async Task<IActionResult> CreateEvent(Event val)
{
    _ctx.Event.Add(val);
    await _ctx.SaveChangesAsync();
    Task.Run(() => SendInvitation(val));
    return Ok();
}

Then I get this output:

Done `Main`.
Done `CreateEvent`.

Then 2 seconds later:

Done `SendInvitation`.

That seems to be what you want.

The short answer is that you have no guarantees that that the execution of that code will complete.

That's why ASP.NET Core has infrastructure for background work: Implementing background tasks in .NET Core 2.x webapps or microservices with IHostedService and the BackgroundService class

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM