在第一次遇到等待后,后台启动的任务没有完成/被终止

精灵鲍勃

在 ASP.NET 应用程序中,我有一个操作,当点击时,以下列方式启动一个新的后台任务:

Task.Factory.StartNew(async () => await somethingWithCpuAndIo(input), CancellationToken.None, TaskCreationOptions.DenyChildAttach | TaskCreationOptions.LongRunning, TaskScheduler.FromCurrentSynchronizationContext());

我不是在等待它,我只是想让它开始,并在后台继续工作。在那之后,我立即向客户端返回响应。

但是出于某种原因,在执行后台工作的初始线程遇到等待方法完成的等待之后,在调试时,我成功地解析了该方法,但是在返回时,执行只是停止并且不会在该点以下继续。

有趣的是,如果我完全等待这个任务(使用 double await),一切都会按预期进行。

这是由于 SynchronizationContext 吗?在我返回响应的那一刻,同步上下文是否被处理/删除?(方法内部正在使用 SynchronizationContext)

如果是因为这个,问题究竟发生在哪里?

A) 当调度器试图在给定的同步上下文上分配工作时,它已经被释放了,所以什么都不会提供

B)在方法执行的某个地方,当我向客户端返回响应时,同步上下文丢失,不管其他任何事情。

C) 完全是别的东西?

如果是 A),我应该能够通过Thread.Sleep()在安排工作和返回响应之间简单地解决这个问题(试过了,没用。)

如果是 B) 我不知道如何解决这个问题。帮助将不胜感激。

加布里埃尔·卢奇

你的评论让你的意图更清晰一些。我认为你想做的是:

  1. 开始任务,不要等待。在后台任务完成之前向客户端返回响应。
  2. 确保该somethingWithCpuAndIo方法可以访问请求上下文。

但,

  1. 不同的线程不会在同一个上下文中,并且
  2. 一旦await命中一个,Task就会返回 a,这也意味着Task.Factory.StartNew返回并继续执行调用方法。这意味着响应返回给客户端。当请求完成时,上下文被释放。

所以你不能真正做你想做的两件事。有几种方法可以解决这个问题:

首先,您可能根本无法在不同的线程上启动它。这取决于何时somethingWithCpuAndIo需要访问上下文。如果它只需要first 之前await的上下文,那么您可以执行以下操作:

public IActionResult MyAction(input) {
    somethingWithCpuAndIo(input); //no await
}

private async Task somethingWithCpuAndIo(SomeThing input) {

    // You can read from the request context here

    await SomeIoRequest().ConfigureAwait(false);

    // Everything after here will run on a ThreadPool thread with no access
    // to the request context.
}

每个异步方法开始同步运行。await给出一个不完整的Task. 所以在这个例子中,somethingWithCpuAndIo将在请求上下文中的同一个线程上开始执行。当它击中 时await, aTask被返回到MyAction,但它不会被等待,因此MyAction完成执行并且 SomeIoRequest()完成之前将响应发送到客户端但是ConfigureAwait(false)告诉它我们不需要在相同的上下文中somethingWithCpuAndIo恢复执行,所以在 ThreadPool 线程上恢复执行。

但是,如果您在第一个awaitin之后不需要上下文,那只会对您有所帮助somethingWithCpuAndIo

您最好的选择是仍然在不同的线程上执行,但将您需要的值从上下文传递到somethingWithCpuAndIo.

而且,出于此处详细描述的原因,请使用Task.Run代替Task.Factory.StartNew

更新:这很可能会导致不可预测的结果,但您也可以尝试将引用传递HttpContext.Current给线程并HttpContext.Current在新线程中进行设置,如下所示:

var ctx = HttpContext.Current;
Task.Run(async () => {
    HttpContext.Current = ctx;
    await SomeIoRequest();
});

但是,这完全取决于您如何使用上下文。HttpContext本身没有实现IDiposable,所以它本身不能被处理。只要您持有对它的引用,垃圾收集器就不会删除它。但是上下文的设计寿命并不比请求长。因此,在将响应返回给客户端之后,上下文的许多部分可能已被处置或以其他方式不可用。测试一下,看看会发生什么爆炸。但是,即使现在什么都没有爆炸,您(或其他人)可能稍后会回到该代码,尝试在上下文中使用其他东西,并在它爆炸时变得非常困惑。它可能会导致一些难以调试的场景。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

接受第一次启动后的通知

第一次启动后的屏幕伪像

数组没有分配给第一次出现,而是完成所有其他行的任务。“异常”错误

Bootstrap 3模态在第一次打开后没有关闭

第一次单击后按钮值没有改变

页面加载后,第一次没有立即调用Ajax

Spring Boot @Schedular 在第一次执行后没有运行

等待第一次等待完成,再等待第二次等待-Async / await

没有第一次加载的数据

Favicon第一次没有出现

第一次尝试 RSepc 没有运气

钩子第一次没有设置状态

javascript没有看到第一次点击?

GridView第一次没有被点击

有角度的。绑定事件一次(第一次启动时)

第一次遇到特定字符串后的子集数据帧

第一次启动Android Studio任务栏图标时出错(Linux)

在筛选操作中使用async / await,在第一次等待调用后不等待完成

在Spring MVC中完成第一次交易后如何调用方法

第一次选择后未显示数组的制表符完成

第一次迭代完成后更改计时器间隔

初始化新活动后,fab.show()第一次没有动画

当 Spotify/Discord 没有在本地保存密码时,它们如何自动登录(在第一次登录后)?

第一次创建类,遇到问题

php在后台启动过程,并等待它完成

第一次使用按钮后,jQuery不重新启动data-id

Keycloak在升级(8 --> 12)和第一次重启后不启动

OEM安装后在第一次启动时播放视频吗?

第一次后无法重新启动我的反应项目