gpt4 book ai didi

asp.net - 对异步同步的 TaskScheduler 和 SynchronizationContext 感到困惑,无法控制同步上下文?

转载 作者:行者123 更新时间:2023-12-04 12:33:37 25 4
gpt4 key购买 nike

问题

我有一个带有简单 Web 服务的 ASP.NET 4.0 WebForms 页面 WebMethod .此方法用作异步/TPL 代码的同步包装器。我面临的问题是内部 Task有时有一个空 SynchronizationContext (我的偏好),但有时同步上下文为 System.Web.LegacyAspNetSynchronizationContext .在我提供的示例中,这并不会真正导致问题,但在我的实际开发场景中可能会导致死锁。

对服务的第一次调用似乎总是使用空同步上下文运行,接下来的几个也可能如此。但是一些快速请求,它开始弹出到 ASP.NET 同步上下文中。

编码

[WebMethod]
public static string MyWebMethod(string name)
{
var rnd = new Random();
int eventId = rnd.Next();
TaskHolder holder = new TaskHolder(eventId);

System.Diagnostics.Debug.WriteLine("Event Id: {0}. Web method thread Id: {1}",
eventId,
Thread.CurrentThread.ManagedThreadId);

var taskResult = Task.Factory.StartNew(
function: () => holder.SampleTask().Result,
creationOptions: TaskCreationOptions.None,
cancellationToken: System.Threading.CancellationToken.None,
scheduler: TaskScheduler.Default)
.Result;

return "Hello " + name + ", result is " + taskResult;
}
TaskHolder的定义存在:
public class TaskHolder
{
private int _eventId;
private ProgressMessageHandler _prg;
private HttpClient _client;

public TaskHolder(int eventId)
{
_eventId = eventId;
_prg = new ProgressMessageHandler();
_client = HttpClientFactory.Create(_prg);
}

public Task<string> SampleTask()
{
System.Diagnostics.Debug.WriteLine("Event Id: {0}. Pre-task thread Id: {1}",
_eventId,
Thread.CurrentThread.ManagedThreadId);

return _client.GetAsync("http://www.google.com")
.ContinueWith((t) =>
{
System.Diagnostics.Debug.WriteLine("Event Id: {0}. Continuation-task thread Id: {1}",
_eventId,
Thread.CurrentThread.ManagedThreadId);

t.Wait();

return string.Format("Length is: {0}", t.Result.Content.Headers.ContentLength.HasValue ? t.Result.Content.Headers.ContentLength.Value.ToString() : "unknown");
}, scheduler: TaskScheduler.Default);
}
}

分析

我对 TaskScheduler.Default 的理解是 ThreadPool调度器。换句话说,线程不会在 ASP.NET 线程上结束。根据 this article , “Task Parallel Library 和 PLINQ 的默认调度程序使用 .NET Framework ThreadPool 来排队和执行工作”。基于此,我预计 SynchronizationContext里面 SampleTask始终为空。

此外,我的理解是,如果 SampleTask将在 ASP.NET SynchronizationContext 上,调用 .ResultMyWebMethod可能陷入僵局。

因为我不会“一直异步”,所以这是一个“同步异步”场景。根据 this article作者:Stephen Toub,在标题为“如果我真的需要“同步而不是异步”怎么办?以下代码应该是一个安全的包装器:
Task.Run(() => holder.SampleTask()).Result

根据 this other article ,同样由 Stephen Toub 撰写,上述内容在功能上应等同于:
Task.Factory.StartNew(
() => holder.SampleTask().Result,
CancellationToken.None,
TaskCreationOptions.DenyChildAttach,
TaskScheduler.Default);

由于在 .NET 4.0 中,我无权访问 TaskCreationOptions.DenyChildAttach , 而我 这是我的问题。但是我在 .NET 4.5 中运行了相同的示例并切换到 TaskCreationOptions.DenyChildAttach它的行为相同(有时会抓取 ASP.NET 同步上下文)。

然后我决定更接近“原始”建议,并在 .NET 4.5 中实现:
Task.Run(() => holder.SampleTask()).Result

这确实有效,因为它总是有一个空同步上下文。其中,有点暗示 Task.Run vs Task.Factory.StartNew article有错吗?

务实的方法是升级到 .NET 4.5 并使用 Task.Run实现,但这将涉及开发时间,我宁愿花在更紧迫的问题上(如果可能的话)。 加号 ,我仍然想弄清楚不同的 TaskScheduler 是怎么回事和 TaskCreationOptions情景。

我偶然发现 TaskCreationOptions.PreferFairness在 .NET 4.0 中似乎表现得如我所愿(所有执行都有一个空同步上下文),但不知道为什么会这样,我很犹豫要不要使用它(它可能不适用于所有场景)。

编辑

一些额外的信息...我已经用 更新了我的示例代码。 死锁,并包含一些调试输出以显示任务正在运行的线程。如果 pre-task 或 continuation-task 输出指示与 WebMethod 相同的线程 id,则会发生死锁。

奇怪的是,如果我不使用 ProgressMessageHandler,我似乎无法复制死锁。我的印象是这无关紧要,无论下游代码如何,我都应该能够使用正确的 Task.Factory.StartNew 在同步上下文中安全地“包装”异步方法。或 Task.Run方法。但这似乎并非如此?

最佳答案

首先,在 ASP.NET 中使用 sync-over-async 通常没有多大意义。您正在承担创建和调度 Task 的开销s,但您不会以任何方式从中受益。

现在,对于你的问题:

My understanding of TaskScheduler.Default is that it's the ThreadPool scheduler. In other words, the thread won't end up on the ASP.NET thread.



好吧,ASP.NET 使用相同的 ThreadPool也。但这在这里并不重要。相关的是,如果您 Wait() (或调用 Result ,相同)在 Task 上计划运行(但尚未开始), TaskScheduler我决定只运行你的 Task同步。这被称为“任务内联”。

这意味着您的 Task最终在 SynchronizationContext 上运行,但实际上并没有通过它安排。这意味着实际上没有死锁的风险。

Thanks to being in .NET 4.0, I don't have access to TaskCreationOptions.DenyChildAttach, and I thought this was my issue.



这与 DenyChildAttach 无关, 没有 Task s 那将是 AttachedToParent .

I've coincidentally found that TaskCreationOptions.PreferFairness in .NET 4.0 appears to behave as I'd wish (all executions have a null sync context), but without knowing why this works, I'm very hesitant to use it (it may not work in all scenarios).



这是因为 PreferFairness安排 Task到全局队列(而不是每个 ThreadPool 线程具有的线程本地队列),似乎 Task来自全局队列的 s 不会被内联。但我不会依赖这种行为,特别是因为它将来会改变。

编辑:

Curiously, if I don't use ProgressMessageHandler, I don't seem able to replicate the deadlock.



没什么好奇怪的,这正是你的问题。 ProgressMessageHandler 报告当前同步上下文的进度。由于任务内联,这就是 ASP.NET 上下文,您通过同步等待来阻止它。

您需要做的是确保 GetAsync()在没有设置同步上下文的线程上运行。我认为最好的方法是调用 SynchronizationContext.SetSynchronizationContext(null) 在调用 GetAsync() 之前并在之后恢复它。

关于asp.net - 对异步同步的 TaskScheduler 和 SynchronizationContext 感到困惑,无法控制同步上下文?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14973379/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com