从 .Net 核心 Web 服务发送不等待的异步电子邮件

Sending an async email without await from a .Net core web service

我有一个网络服务 .Net core2,它具有某些发送电子邮件的方法。我使用 smtpclient.sendemailasync.

它工作正常
    public async Task<bool> SendEmailAsync(MailMessage email)
    {
        try
        {
            if (string.IsNullOrWhiteSpace(emailFrom)) email.From = new MailAddress(emailFrom);
            using (SmtpClient client = getSMTPClientInstance())
            {
                await client.SendMailAsync(email);

            }
            return true;
        }
        catch (Exception ex)
        {
            Log.Error(ex, "Error sending email in EmailService.SendEmailAsync");
            return false;
        }
    }

唯一的问题是某些 SMTP 服务器的响应时间有点太长。我想设置电子邮件,将其排队并 return 而无需等待结果。

仅使用未等待的异步有两个原因;

  1. 在 asp

  2. 中继续请求上下文之外的方法是不可靠的
  3. 我需要访问我的 entity framework 的数据库上下文来写日志

我必须允许外部或内部 SMTP(我的客户指定),所以集合文件夹是不可能的 - 至少在没有管理它的服务的情况下是不可能的。

我怎样才能做到这一点?我是否需要编写管理此服务的服务?如果是这样,我将如何在我的 .Net Core 应用程序中执行此操作,请记住该服务还需要访问 EF 上下文以写入日志

更新

.NetCore DI 中有专门用于此的管道。请参阅下面我的附加答案。使用 IServiceScopeFactory

您可以调用 Page 对象的 RegisterAsyncTask 方法。这将向 ASP.NET 运行时发出信号,以确保在终止请求上下文之前完成这些操作:

示例:

public void Page_Load(object sender, EventArgs e)
{
    RegisterAsyncTask(new PageAsyncTask(LoadSomeData));
}

public async Task LoadSomeData()
{

    var clientcontacts = Client.DownloadStringTaskAsync("api/contacts");
    var clienttemperature = Client.DownloadStringTaskAsync("api/temperature");
    var clientlocation = Client.DownloadStringTaskAsync("api/location");

    await Task.WhenAll(clientcontacts, clienttemperature, clientlocation);

    var contacts = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Contact>>(await clientcontacts);
    var location = Newtonsoft.Json.JsonConvert.DeserializeObject<string>(await clientlocation);
    var temperature = Newtonsoft.Json.JsonConvert.DeserializeObject<string>(await clienttemperature);

    listcontacts.DataSource = contacts;
    listcontacts.DataBind();
    Temparature.Text = temperature;
    Location.Text = location;
}

https://www.hanselman.com/blog/TheMagicOfUsingAsynchronousMethodsInASPNET45PlusAnImportantGotcha.aspx

因此,虽然我已经标记了一个答案,但对于我的具体示例,有几个选项是更好的解决方案。首先是使用像 hangfire 这样的库来安排任务的选项——尽管从技术上讲这不是问题的答案。

.net core 中更好的解决方案是使用 IServiceScopeFactory

使用 IServiceScopeFactory,您可以重新确定任务的范围,以便在请求完成时它不会超出范围。我直接在控制器中执行了以下操作(后来我转而使用 hangfire 方法,但这很有效)。如您所见,在控制器代码继续执行的同时,异步任务在一个新的未等待线程中被触发。

        var task = Task.Run(async () =>
        {
            using (var scope = _serviceScopeFactory.CreateScope())
            {
                var service = scope.ServiceProvider.GetRequiredService<ApprovalService>();
                await service.sendResponseEmailAsync(approvalInfo.ApprovalId, userID, approvalInfo.emailTo, approvalInfo.ccTo);
            }
        });