【问题标题】:Start a task with clean AsyncLocal state以干净的 AsyncLocal 状态启动任务
【发布时间】:2017-11-29 04:31:01
【问题描述】:

在 ASP.NET Core 中,我使用IHttpContextAccessor 来访问当前的HttpContextHttpContextAccessor uses AsyncLocal<T>.

在一种情况下,我试图从请求中启动Task,它应该在请求结束后继续运行(长时间运行,后台任务),并且我试图将它与当前执行上下文分离这样IHttpContextAccessor 返回null(否则我访问的是无效的HttpContext)。

我尝试了Task.RunThread.Start,但每次,上下文似乎都会延续,IHttpContextAccessor 无论我尝试什么都会返回旧上下文(有时甚至是来自其他请求的上下文????)。

如何启动具有干净 AsyncLocal 状态的任务,以便 IHttpContextAccessor 返回 null

【问题讨论】:

  • 无论如何,您都应该避免在 ASP.NET Core 中运行/生成自己的线程/后台任务,这会扰乱 ASP.NET Core 处理传入请求的线程的方式。您应该使用一种本地或远程消息总线正确执行此操作,并让真正的后台进程(即不托管 asp.net 核心的控制台应用程序)处理它
  • 至少我应该能够创建自己的线程而不会干扰 Kestrel 的请求处理。即使这样也不起作用,并捕获了一些随机的HttpContext
  • Task.Run(...)等有一个参数可以设置调度器。您可以在此处设置默认调度程序(ASP.NET Core 运行自己的调度程序),即Task.Run(() => DoSomething(), TaskScheduler.Default)。但同样,我不建议您这样做,您应该考虑如上所述的真实后台进程,因为后台任务所占用的线程是不能用于接受请求的线程。 ASP.NET Core 应该只使用 await/async 进行真正的异步操作,而不是 CPU 密集型的东西或后台任务
  • 也是有用的资源hanselman.com/blog/HowToRunBackgroundTasksInASPNET.aspxhaacked.com/archive/2011/10/16/…,虽然它主要是关于 ASP.NET 4,但它的概念仍然适用
  • 我没有使用 IIS。与 IIS 不同,Kestrel 不回收其 AppDomain(事实上,它根本没有 AppDomain)。无论如何,在我的用例中,被中断的后台任务都不是问题。同样,Thread.StartNew 产生了一个并非来自线程池的全新线程,因此您之前的评论是题外话。

标签: asp.net-core .net-core asp.net-core-1.1


【解决方案1】:

您可以等待长时间运行的任务并将HttpContext 设置为空在等待内。对于任务之外的所有代码都是安全的。

[Fact]
public async Task AsyncLocalTest()
{
    _accessor = new HttpContextAccessor();

    _accessor.HttpContext = new DefaultHttpContext();

    await LongRunningTaskAsync();

    Assert.True(_accessor.HttpContext != null);
}

private async Task LongRunningTaskAsync()
{
    _accessor.HttpContext = null;
}

你可以运行一个单独的线程,没关系。

Task.Run(async () => await LongRunningTaskAsync());

只需让任务等待清理HttpContext 仅用于任务的异步上下文。

【讨论】:

  • 似乎对我不起作用。从长时间运行的任务内部访问 httpContextAccessor.HttpContext 仍会返回非空 HttpContext。此外,我不能等待请求中的长时间运行任务,因为我希望请求现在完成,无论长时间运行的任务运行多长时间。
  • @Flavien,您可以使用Task.Run 在单独的线程中启动任务,没关系。关键是在调用者和任务之间放置await。例如:Task.Run(async () => await LongRunningTaskAsync());
  • 我试过了,但即使使用await,它似乎也没有什么不同。 HttpContext 仍然存在。
  • 好的,只要我重用同一个 HttpContextAccessor 实例,这个方法就可以工作。
  • @Flavien,是的,它应该被注册为单例。
【解决方案2】:

迟到的答案,但可能对其他人有用。您可以通过在提交任务时抑制 ExecutionContext 流来做到这一点:

ExecutionContext.SuppressFlow();

var task = Task.Run(async () => {
    await LongRunningMethodAsync();
});

ExecutionContext.RestoreFlow();

或更好:

using (ExecutionContext.SuppressFlow()) {
    var task = Task.Run(async () => {
        await LongRunningMethodAsync();
    });
}

这将防止在提交的任务中捕获 ExecutionContext。因此它将有一个“干净”的 AsyncLocal 状态。

如上所述,如果它是 CPU 密集型后台工作,我不会在 ASP.net 中执行此操作。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-29
    • 2015-09-30
    • 1970-01-01
    • 2015-02-21
    • 2014-07-24
    • 2019-06-08
    • 2014-03-04
    • 1970-01-01
    相关资源
    最近更新 更多