ASP.NET Identity中的零星应用程序死锁

Suc*_*man 6 c# asp.net deadlock task asp.net-identity

我有一个使用ASP.NET Identity和OWIN的ASP.NET 4.7.2 MVC 5应用程序OAuthAuthorizationServerMiddleware。在该RefreshTokenProvider.OnReceive方法中,我访问的SignInManager.CreateUserIdentity方法是ASP.NET Identity方法,该方法在内部使用AsyncHelper(请参见下文)来调用异步方法。每隔一段时间(在繁忙的系统上通常间隔数月),这就会崩溃并锁定整个应用程序。从我收集的内存转储中,有565个线程在内部等待GetResult 在此处输入图片说明

// Copyright (c) Microsoft Corporation, Inc. All rights reserved.
// Licensed under the MIT License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.AspNet.Identity
{
    internal static class AsyncHelper
    {
        private static readonly TaskFactory _myTaskFactory = new TaskFactory(CancellationToken.None,
            TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.Default);

        public static TResult RunSync<TResult>(Func<Task<TResult>> func)
        {
            var cultureUi = CultureInfo.CurrentUICulture;
            var culture = CultureInfo.CurrentCulture;
            return _myTaskFactory.StartNew(() =>
            {
                Thread.CurrentThread.CurrentCulture = culture;
                Thread.CurrentThread.CurrentUICulture = cultureUi;
                return func();
            }).Unwrap().GetAwaiter().GetResult();
        }

        public static void RunSync(Func<Task> func)
        {
            var cultureUi = CultureInfo.CurrentUICulture;
            var culture = CultureInfo.CurrentCulture;
            _myTaskFactory.StartNew(() =>
            {
                Thread.CurrentThread.CurrentCulture = culture;
                Thread.CurrentThread.CurrentUICulture = cultureUi;
                return func();
            }).Unwrap().GetAwaiter().GetResult();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

内存转储显示580多个任务,这些任务处于RanToCompletion状态。GetResult考虑到“任务”已完成,我无法诊断为什么不成功,也无法确定为什么许多线程在几个月前正常工作时会迅速堆积在那里,以及为什么整个应用程序都无法响应,即使他们没有遵循这条道路。现在,这多次导致生产中断,除了重新启动之外,我没有其他解决方法。

我尝试使用该OnReceiveAsync方法,但这些方法似乎毫无意义,因为在调用它们之前,有一个小片段:

if (OnReceiveAsync != null && OnReceive == null)
{
    throw new InvalidOperationException(Resources.Exception_AuthenticationTokenDoesNotProvideSyncMethods);
}
Run Code Online (Sandbox Code Playgroud)

编辑:复制和问题的解释:

此WebAPI 2控制器可以重现该问题

if (OnReceiveAsync != null && OnReceive == null)
{
    throw new InvalidOperationException(Resources.Exception_AuthenticationTokenDoesNotProvideSyncMethods);
}
Run Code Online (Sandbox Code Playgroud)

这个程序产生负载

using System.Threading.Tasks;
using System.Web.Http;

namespace WebApplication65.Controllers
{
    public class ValuesController : ApiController
    {
        public string Post([FromBody]string value)
        {
            return AsyncHelper.RunSync(PostAsync);
        }

        private async Task<string> PostAsync()
        {
            await Task.Delay(10);
            return "Hello World";
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

该程序将在几毫秒内对100个请求进行命中。这将导致所有以前可用的线程陷入困境,AsyncHelper.RunSync并且有更多的请求排队并且根本没有发送响应。ThreadPool注意到它需要更多线程,但是每秒只会增加大约一个线程,这将立即陷入AsyncHelper.RunSync尝试为排队请求之一提供服务的过程。大约一分钟后,当ThreadPool扩展了大约100个其他线程来处理100个请求时,所有挂起的请求将在眨眼间响应,并且应用程序将再次响应。

我的应用程序的区别在于,请求不断传入,而示例中一次仅发送100个请求。这意味着我的应用无法从这种情况下恢复,因为ThreadPool的创建新线程的速度不足以跟上传入的请求。

创建我的异步复制ReceiveRefreshToken方法和填充OnReceiveAsync除了OnReceive出现,以避免该问题足以得不到用尽的线程池。

Igo*_*gor 2

我认为应用程序耗尽了线程池。

AsyncHelper 使用 TaskScheduler.Default ,这意味着在 ThreadPool 上执行。它应该可以正常工作,直到所有线程都被阻止(例如,许多用户和/或来自令牌端点的响应缓慢)并且没有更多的空闲线程可以继续。这会导致死锁(您可以在此处阅读更多相关信息)。由于应用程序只有一个线程池,如果线程池耗尽,所有线程池都会停止。

无论如何,我会尝试使用 OnReceiveAsync 并从这里以异步方式创建用户。您可能需要一个虚拟的 OnReceive 以避免异常。

或者,您可以尝试扩展线程池的大小,但这只是一个短期解决方案,不能保证始终有效。