asp.net – 由TaskScheduler和SynchronizationContext混淆同步异步,无法控制同步上下文?

问题

我有一个ASP.NET 4.0 WebForms页面,其中包含一个简单的Web服务WebMethod.此方法用作异步/ TPL代码的同步包装器.我面临的问题是内部任务有时会有一个空的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}",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,“任务并行库和PLINQ的默认调度程序使用.NET Framework ThreadPool来排队和执行工作”.基于此,我希望SampleTask中的SynchronizationContext始终为null.

此外,我的理解是,如果SampleTask位于ASP.NET SynchronizationContext上,则MyWebMethod中对.Result的调用可能会死锁.

因为我不会“一直向下”,所以这是一个“同步异步”的场景.由Stephen Toub撰写的每this article,在标题为“如果我确实需要的内容”的部分中同步异步“?”以下代码应该是一个安全的包装器:

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场景发生了什么.

我巧合地发现.NET 4.0中的TaskCreationOptions.PreferFairness似乎表现得像我希望的那样(所有执行都有一个空同步上下文),但不知道为什么会这样,我对使用它非常犹豫(它可能不会在所有场景中工作).

编辑

一些额外的信息……我用一个做死锁的代码更新了我的示例代码,并包含一些调试输出以显示正在运行任务的线程.如果任务前或继续任务输出指示与WebMethod相同的线程ID,则会发生死锁.

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

解决方法

首先,在ASP.NET中使用sync-over-async通常没有多大意义.您将承担创建和安排任务的开销,但您无法以任何方式从中受益.

现在,问你的问题:

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.但这并不重要.有意义的是,如果您在计划运行的任务(但尚未启动)上等待()(或调用结果,这是相同的),则TaskScheduler我决定同步运行您的任务.这被称为“任务内联”.

这意味着您的任务最终在SynchronizationContext上运行,但实际上并没有通过它进行调度.这意味着实际上没有死锁的风险.

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

这与DenyChildAttach无关,没有任何可以成为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线程所具有的线程本地队列),并且似乎不会内联全局队列中的Tasks.但我不会依赖这种行为,特别是因为它可以在将来改变.

编辑:

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

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

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


判断URL文件是不是在于在。private static bool UrlIsExist(string url){ System.Uri u = null; try { u = new Uri(url); } catch { return false; } bool isExist = false;
由于在.net中,Request时出现有HTML或Javascript等字符串时,系统会认为是危险性值。立马报错。解决方案一:在.aspx文件头中加入这句:解决方案二:修改web.config文件:因为validateRequest默认值为true。只要设为false即可。
public static bool ProcessIdCard(this string idCard, out DateTime birthday, out string genderName) { bool result; birthda...
如果你在GridView控件上设置 AllowPaging=&quot;true&quot; or AllowSorting=&quot;true&quot; 而没有使用使用数据源控件 DataSource (i.e. SqlDataSource, ObjectDataSource),运行则会出现下
protected void Page_Load(object sender, EventArgs e){ ScriptManager sm = Page.Master.FindControl(&quot;ScriptManager1&quot;) as ScriptManager; if (sm
1. install all features in IIS2. Try the following steps to register it.run %windir%\Microsoft.NET\Framework\v4.0.21006\aspnet_regiis.exe -i或运行,跳出如下错误
一般来说一个 HTML 文档有很多标签,比如“”、“”、“”等,想把文档中的 img 标签提取出来并不是一件容易的事。由于 img 标签样式变化多端,使提取的时候用程序寻找并不容易。于是想要寻找它们就必须写一个非常健全的正则表达式,不然有可能会找得不全,或者找出来的不是正确的 img 标签。我们可以
asp.net updatepanel 局部刷新,导致JS不能加载,而无法使用,而且 updatepanel会刷两次,郁闷的。解决方法如下:
FileHandlerhttp://www.cnblogs.com/vipsoft/p/3627709.htmlUpdatePanel无法导出下载文件:http://www.cnblogs.com/vipsoft/p/3298299.html//相对路径下载。path: ~/DownLoad///p
本地能上传文件,部署到服务器上就报Cannot access a closed file 错误,以下是解决方法: 最重要是requestLengthDiskThreshold此属性设置输入流缓冲阈值。
http://tool.oschina.net/commons字符十进制转义字符&quot;&amp;#34;&amp;quot;&amp;&amp;#38;&amp;amp;&amp;#62;&amp;gt;不断开空格(non-breaking space)&amp;#160;HTML特殊转义字符
1、2两步为推荐做法1. 将MySql.Data.dll放到 bin目录下面,或都安装mysql-connector-net-6.0.0.msi2.web.config 添加如下节点,注册版本号一致 3.全局配置在C:\WINDOWS\Microsoft.NET\Framework\v2.0.507
C# 跳转新页面string url = &quot;http://www.vipsoft.com.cn&quot;;ResponseRedirect.Redirect(Response, url, &quot;_blank&quot;, &quot;&#39;toolbar=0,scrollbar
.NET Core 在其上下文中,该请求的地址无效。 看了端口,发现没被占用,后来发现是IP地址变了 改成正确的IP就可以了。
datatable是一个jquery扩展的表格插件。其提供了强大的表格功能。官方地址:http://www.datatables.net/在官方示例中,对于表格的是否可排序是在初始化中设置的一个值来决定的$(&quot;.datatable-simplified&quot;).dataTable(
Html table 细边框 导航页档 军事 历史 ...
C# 跳转新页面判断URL文件是不是在于在。C# 指定物理目录下载文件,Response.End导致“正在中止线程”异常的问题public class FileHandler { public static bool DownLoadFile(string path, string fileName
由于将IE11升级到了 11 之前的网站无法正常使用,如果是开发人员碰到之问题,使用了微软的asp.net 控件,那么将服务器的.net framework 升级到 4.5http://www.microsoft.com/en-us/download/details.aspx?id=30653如果浏
引言 本文从Linux小白的视角, 在CentOS 7.x服务器上搭建一个Nginx-Powered AspNet Core Web准生产应用。 在开始之前,我们还是重温一下部署原理,正如你所常见的.Net Core 部署图: 在Linux上部署.Net Core App最好的方式是在Linux机器