简体   繁体   English

当我们不等待异步方法时会发生什么

[英]What happens when we don't await an async method

I have a .NET CORE 2 backend. 我有一个.NET CORE 2后端。 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? 如果我不await sendFn()那么它本质上将是一种即发即await sendFn()方法吗? 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. 我正在阅读另一个必须执行sendFn().ContinueWith(t => throw(t)) stackoverflow线程,以便能够捕获该异常,因为它将在另一个线程中。

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. 我认为有些地方我不能使用await (如果可行),但是有些事情会改变数据库上下文,因此,如果我不等待它们,我可能会遇到某些情况,即某些人正在访问同一数据库上下文。

[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: 然后我像这样更新了SendInvitation

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: 然后2秒后:

Done `SendInvitation`.
Done `CreateEvent`.

If I simply change CreateEvent to this: 如果我只是将CreateEvent更改为此:

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: 然后2秒后:

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 这就是ASP.NET Core具有用于后台工作的基础结构的原因: 使用IHostedService和BackgroundService类在.NET Core 2.x webapps或微服务中实现后台任务

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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