代码之家  ›  专栏  ›  技术社区  ›  user6728767

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

  •  1
  • user6728767  · 技术社区  · 7 年前

    我有一个.NET核心2后端。在我的一个控制器端点中,我正在创建通过电子邮件发送的邀请。这似乎是终结点上的一个巨大瓶颈,考虑到这一点,我真的不需要等待这些邀请。如果这封邮件发不出去,我也无能为力。

    如果我不这样做 await sendFn() 它本质上是一种失火的方法吗?我在读另一个stackoverflow线程 sendFn().ContinueWith(t => throw(t)) 以便能够捕获异常,因为它将在另一个线程中。

    我在代码库周围有类似的邮件功能。他们每个人做的事情略有不同,但有没有一个服务,我可以做包装这些使他们火灾和忘记?我想有些地方我不能用 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.
    
       }
    }
    

    我正在向服务器发送有关事件的数据。创建事件并将其保存到数据库后,我将为每个人创建邀请。这些邀请也是数据库项。然后我发一封电子邮件。我最担心的是,如果我放弃等待,那么当我创建邀请时,它可能会与其他地方或下一个迭代的数据库上下文冲突。

    2 回复  |  直到 7 年前
        1
  •  3
  •   Enigmativity    7 年前

    为了让您的代码编译和运行,我必须进行以下更改:

    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");
        }
    }
    

    我还必须写下这个线束代码:

    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(() => { }); }
    }
    

    然后我更新了 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`.");
    }
    

    现在,我可以像这样运行它:

    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`.");
    

    输出:

    Done `Main`.
    

    然后2秒后:

    Done `SendInvitation`.
    Done `CreateEvent`.
    

    如果我只是换衣服 CreateEvent 为此:

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

    然后我得到这个输出:

    Done `Main`.
    Done `CreateEvent`.
    

    然后2秒后:

    Done `SendInvitation`.
    

    这似乎是你想要的。

        2
  •  3
  •   Paulo Morgado    7 年前

    简而言之,您不能保证代码的执行会完成。

    这就是为什么ASP.NET核心具有用于后台工作的基础结构: Implementing background tasks in .NET Core 2.x webapps or microservices with IHostedService and the BackgroundService class