加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

c# – AspNetSynchronizationContext并等待ASP.NET中的延续

发布时间:2020-12-15 06:18:42 所属栏目:百科 来源:网络整理
导读:在异步ASP.NET Web API控制器方法等待之后,我注意到了一个意想不到的(我会说,一个冗余的)线程切换. 例如,下面我会期望在#2和3#的位置看到相同的ManagedThreadId,但是我经常看到#3的不同线程: public class TestController : ApiController{ public async Ta
在异步ASP.NET Web API控制器方法等待之后,我注意到了一个意想不到的(我会说,一个冗余的)线程切换.

例如,下面我会期望在#2和3#的位置看到相同的ManagedThreadId,但是我经常看到#3的不同线程:

public class TestController : ApiController
{
    public async Task<string> GetData()
    {
        Debug.WriteLine(new
        {
            where = "1) before await",thread = Thread.CurrentThread.ManagedThreadId,context = SynchronizationContext.Current
        });

        await Task.Delay(100).ContinueWith(t =>
        {
            Debug.WriteLine(new
            {
                where = "2) inside ContinueWith",context = SynchronizationContext.Current
            });
        },TaskContinuationOptions.ExecuteSynchronously); //.ConfigureAwait(false);

        Debug.WriteLine(new
        {
            where = "3) after await",context = SynchronizationContext.Current
        });

        return "OK";
    }
}

我已经看过了AspNetSynchronizationContext.Post的实现,基本上归结为:

Task newTask = _lastScheduledTask.ContinueWith(_ => SafeWrapCallback(action));
_lastScheduledTask = newTask;

因此,继续在ThreadPool上安排,而不是内联.在这里,ContinueWith使用TaskScheduler.Current,这在我的经验中总是ASP.NET中的ThreadPoolTask??Scheduler实例(但不一定是这样,见下文).

我可以使用ConfigureAwait(false)或自定义等待程序来消除冗余的线程切换,但这将消除HTTP请求的状态属性(如HttpContext.Current)的自动流.

AspNetSynchronizationContext.Post当前实现的另一个副作用.在以下情况下会导致死锁:

await Task.Factory.StartNew(
    async () =>
    {
        return await Task.Factory.StartNew(
            () => Type.Missing,CancellationToken.None,TaskCreationOptions.None,scheduler: TaskScheduler.FromCurrentSynchronizationContext());
    },scheduler: TaskScheduler.FromCurrentSynchronizationContext()).Unwrap();

这个例子,虽然有点设计,显示了如果TaskScheduler.Current是TaskScheduler.FromCurrentSynchronizationContext(),即由AspNetSynchronizationContext构成的可能会发生什么.它不使用任何阻止代码,并且将在WinForms或WPF中顺利执行.

AspNetSynchronizationContext的这种行为与v4.0实现不同(仍然是LegacyAspNetSynchronizationContext).

那么这种变化的原因是什么呢?我以为,这个想法可能是为了减少死锁的差距,但是当使用Task.Wait()或Task.Result时,目前的实现仍然可能导致死锁.

海事组织,更适合这样说:

Task newTask = _lastScheduledTask.ContinueWith(_ => SafeWrapCallback(action),TaskContinuationOptions.ExecuteSynchronously);
_lastScheduledTask = newTask;

或者,至少我期望它使用TaskScheduler.Default而不是TaskScheduler.Current.

如果我启用LegacyAspNetSynchronizationContext与< add key =“aspnet:UseTaskFriendlySynchronizationContext”value =“false”/>在web.config中,它可以按需要工作:同步上下文被安装在等待已经完成的任务已经结束的线程上,并且继续在那里同步执行.

解决方法

继续发送到新线程而不是内联是有意的.我们打破这个:

你正在打电话给Task.Delay(100). 100毫秒后,基础任务将转换到完成的状态.但是,这种转换将在任意的ThreadPool / IOCP线程上发生;它不会发生在ASP.NET同步上下文下的线程上.
> .ContinueWith(…,ExecuteSynchronously)将导致Debug.WriteLine(2)发生在将Task.Delay(100)转换为终端状态的线程上. ContinueWith本身将返回一个新的Task.
你正在等待[2]返回的任务.由于完成Task [2]的线程不在ASP.NET同步上下文的控制之下,所以异步/等待机制将调用SynchronizationContext.Post.这种方法总是以异步方式签约.

异步/等待机制确实有一些优化来在完成的线程上内联执行连续性,而不是调用SynchronizationContext.Post,但是如果完成的线程当前正在正在发送的同步上下文中运行,那么该优化才会启动.这在上面的示例中不是这样,因为[2]运行在任意线程池线程上,但需要将其返回到AspNetSynchronizationContext以运行[3]继续.这也解释了为什么如果使用.ConfigureAwait(false),线程跳没有发生:[3]继续可以在[2]中内联,因为它将在默认的同步上下文下调度.

对于您的其他问题:Task.Wait()和Task.Result,新的同步上下文不是为了减少相对于.NET 4.0的死锁条件. (事实上??,在新的同步上下文中比在旧的上下文中更容易获得死锁).新的同步上下文旨在实现.Post(),它与async / await机制一起播放,旧的同步上下文在做错时惨败. (旧的同步上下文的.Post()的实现是阻止调用线程,直到同步原语可用,然后内联派遣回调.)

调用Task.Wait()和Task.Result从一个任务不知道要完成的请求线程仍然可能会导致死锁,就像从Win Forms或WPF应用程序中的UI线程调用Task.Wait()或Task.Result .

最后,Task.Factory.StartNew的奇怪可能是一个实际的bug.但是,直到有一个实际(非设计)的方案来支持这一点,球队不会再倾向于进一步调查.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读