Entity framework 从.Net核心web服务发送异步电子邮件而不等待

Entity framework 从.Net核心web服务发送异步电子邮件而不等待,entity-framework,async-await,asp.net-core-webapi,Entity Framework,Async Await,Asp.net Core Webapi,我有一个webservice.NETCore2,它有一些发送电子邮件的方法。我使用smtpclient.sendemailasync可以很好地工作 唯一的问题是,一些SMTP服务器的响应时间有点太长。我想设置电子邮件,排队并返回,而不必等待结果 仅仅使用一个未等待的异步是出于两个原因 在asp中,在请求上下文之外继续方法是不可靠的 我需要访问实体框架的数据库上下文才能编写日志 我必须允许客户端指定外部或内部SMTP,因此不可能创建集合文件夹-至少在没有管理它的服务的情况下是这样 我怎样才能做到这

我有一个webservice.NETCore2,它有一些发送电子邮件的方法。我使用smtpclient.sendemailasync可以很好地工作

唯一的问题是,一些SMTP服务器的响应时间有点太长。我想设置电子邮件,排队并返回,而不必等待结果

仅仅使用一个未等待的异步是出于两个原因

在asp中,在请求上下文之外继续方法是不可靠的

我需要访问实体框架的数据库上下文才能编写日志

我必须允许客户端指定外部或内部SMTP,因此不可能创建集合文件夹-至少在没有管理它的服务的情况下是这样

我怎样才能做到这一点?我需要写一个服务来管理这个吗?如果是这样的话,我将如何在我的.Net核心应用程序中做到这一点,请记住该服务还需要访问EF上下文才能编写日志

更新

NetCore DI中有管道可供使用,尤其适用于此。请参阅下面我的补充答案。使用IServiceScopeFactory

您可以调用页面对象上的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;
}
您可以在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;
}

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

net内核中更好的解决方案是使用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);
            }
        });

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

net内核中更好的解决方案是使用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);
            }
        });