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

5
我有一个.NET CORE 2后端。在我的控制器端点之一中,我正在创建通过电子邮件发送的邀请。这似乎是端点上的一个巨大瓶颈,并且经过思考,我实际上不需要等待这些邀请。如果电子邮件无法发送,我也无能为力。
如果我不执行await sendFn()会不会变成一种“fire and forget”的方法?我在另一个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.

   }
}

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


2
如果您能提供一个 [mcve],那就太好了。 - Enigmativity
我添加了一个非常简化的代码版本。 - user6728767
1
@mason - 在我看来,fire and forget 完全没问题。互联网上的 UDP 协议就是基于 fire and forget 的。DNS 系统也是使用它工作的。 - Enigmativity
1
@mason - OP告诉我们,如果邮件发送失败,他也无能为力,所以不必担心这些邀请是否发送成功。 - Enigmativity
1
@mason - 你是对的,这些都是好的东西。然而,楼主说这并不重要,所以在回答这个问题时,它只是噪音,而不是信号。 - Enigmativity
显示剩余6条评论
2个回答

5
为了让你的代码编译并运行,我不得不做出一些改变:
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`.");

这将输出:

完成 `Main`。

然后2秒钟后:

完成 `SendInvitation`。
完成 `CreateEvent`。

如果我只是将 CreateEvent 改成这样:

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

然后我得到了以下输出:

完成 `Main`。
完成 `CreateEvent`。

2秒钟后:

完成 `SendInvitation`。

看起来这就是你想要的。


感谢您提供详细的答案。很抱歉我没有包含测试代码,这完全是我的疏忽。由于某种原因,我认为那些.NET东西已经编译了。非常感谢您花时间写下这些内容。当您说“如果我只是将CreateEvent更改为这个”,它之前是什么?另外,continueWith只是确保在任务完成时运行给定的lambda表达式,但在原始上下文中呢? - user6728767
@user6728767 - CreateEvent 的两个版本都在我的答案中。ContinueWith 只是在主任务完成后运行继续任务 - 我不确定上下文(您是否指的是线程?)。 - Enigmativity
那么在我的情况下,我只需要这样做:CreateEvent.ContinueWith(e => // handle exception, , TaskContinuationOptions.OnlyOnFaulted)。这将以“Fire and Forget”的方式运行CreateEvent函数,但仅在发生异常时调用继续操作,对吗? - user6728767
@user6728767 - 如果我正确理解了继续选项,那么它将取消原始任务。尝试 Task.Run(() => 42).ContinueWith(e => -1, TaskContinuationOptions.OnlyOnFaulted); - 它会抛出异常。 - Enigmativity

3

4
您是否有参考文档或其他地方明确指出非等待任务不能保证完成?我的问题恰恰是这样 - 如果我在ASP.NET Core控制器中不等待,是否有可能在任务完成之前关闭我的控制器?我知道有后台服务的基础设施,但这对于简单任务而言有些过度。 - Brian S
你有参考文档或其他地方的引用可以明确说明非等待任务保证完成吗? - Paulo Morgado
2
谢谢回复,保罗。不,我没有参考资料表明它一定会完成,但我也没有参考资料表明它不会完成。这就是为什么我在问的原因。我假设它是后者(不保证完成),但我很想通过一些可靠的来源来确认我的假设。 - Brian S

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接