C# 与MS AsyncHelper同步运行异步方法的Null异常

C# 与MS AsyncHelper同步运行异步方法的Null异常,c#,asp.net-mvc,razor,asynchronous,synchronous,C#,Asp.net Mvc,Razor,Asynchronous,Synchronous,由于无法从子窗口(@Html.Action)调用运行异步方法,我一直在寻找从非异步方法运行异步任务的最简单方法。这将允许我的main菜单controllerMenu操作在这样注入时仍能工作(而不必移动到VM或Ajax解决方案): 另一个更新: 如果简化的candidateisnyc有效,则故障出现在相关代码内: // This works public async Task<int> CandidateIdAsync() { return 0; } //这很管用 公共异步任

由于无法从子窗口(
@Html.Action
)调用运行异步方法,我一直在寻找从非异步方法运行异步任务的最简单方法。这将允许我的
main菜单
controller
Menu
操作在这样注入时仍能工作(而不必移动到VM或Ajax解决方案):

另一个更新:

如果简化的
candidateisnyc
有效,则故障出现在相关代码内:

// This works
public async Task<int> CandidateIdAsync()
{
    return 0;
}
//这很管用
公共异步任务CandidateAsync()
{
返回0;
}
以下是该代码的其余部分:

public class CurrentCandidate : ICurrentCandidate
{
    private readonly ApplicationDbContext _applicationDbContext;
    private readonly IApplicationUserManager _userManager;
    private readonly ICandidateStore _candidateStore;

    public CurrentCandidate(ApplicationDbContext applicationDbContext, ICandidateStore candidateStore, IApplicationUserManager userManager)
    {
        this._candidateStore = candidateStore;
        this._applicationDbContext = applicationDbContext;
        this._userManager = userManager;    // new ApplicationUserManager(new UserStore<ApplicationUser>(this._applicationDbContext));
    }

    public async Task<ApplicationUser> ApplicationUserAsync()
    {
        var applicationUser = await this._userManager.FindByIdAsync(HttpContext.Current.User.Identity.GetUserId());
        return applicationUser;
    }

    public bool IsAuthenticated()
    {
        return HttpContext.Current.User.Identity.IsAuthenticated;
    }

    public async Task<int> CandidateIdAsync()
    {
        var applicationUser = await this.ApplicationUserAsync();
        if (applicationUser != null)
        {
            return applicationUser.CandidateId.GetValueOrDefault();
        }
        return 0;
    }
}
公共类CurrentCandidate:iccurrentCandidate
{
私有只读应用程序上下文\u应用程序上下文;
专用只读IApplicationUserManager\u userManager;
私有只读ICandidateStore _candidateStore;
public CurrentCandidate(ApplicationDbContext ApplicationDbContext,ICandidateStore candidateStore,IAApplicationUserManager用户管理器)
{
这个._candidateStore=candidateStore;
这是。_applicationDbContext=applicationDbContext;
this.\u userManager=userManager;//新的ApplicationUserManager(新的UserStore(this.\u applicationDbContext));
}
公共异步任务ApplicationUserAsync()
{
var applicationUser=等待此消息。_userManager.FindByIdAsync(HttpContext.Current.User.Identity.GetUserId());
返回应用程序用户;
}
公共布尔值已验证()
{
返回HttpContext.Current.User.Identity.IsAuthenticated;
}
公共异步任务CandidateAsync()
{
var applicationUser=wait this.ApplicationUserAsync();
if(applicationUser!=null)
{
返回applicationUser.CandidateId.GetValueOrDefault();
}
返回0;
}
}

同步运行异步方法的标准方法是

var r = Task.Run( () => MyAsynchMethod(args)).Result;

两者都不会导致死锁,因为它们将在没有ASP.Net同步上下文的情况下运行至少异步返回部分方法

现在,大多数与ASP.Net相关的方法(如本例中的呈现或标识)都希望将
HttpContext.Current
正确设置为“Current”上下文-当代码在未“输入”ASP.Net同步上下文的线程上运行时,情况显然不是这样(您也会失去当前区域性,但至少不会导致NRE)

肮脏的解决方法-手动设置上下文,但您会遇到从多个线程并行访问同一上下文的危险-如果小心使用,它可能是正常的(即,如果“main”请求线程正在等待
。Result
其他线程可以使用它):

请注意,您最好恢复上下文并同时设置/恢复
CurrentCulture
/
CurrentUICulture

我一直在寻找从非异步方法运行异步任务的最简单方法

这一点已经讨论过很多次了,没有一种解决方案适用于每种场景

一般而言,这些方法是:

  • 块(使用
    Result
    GetAwaiter().GetResult()
    )。除非您始终使用
    ConfigureAwait(false)
    ,并且您调用的所有代码也始终使用
    ConfigureAwait(false)
    。但是,请注意,您的代码不能使用
    ConfigureAwait(false)
    除非它实际上不需要ASP.NET上下文
  • 嵌套消息循环。这可以使用我的AsyncEx库中的类似于
    AsyncContext
    的内容。但是,有许多ASP.NET API隐式假定当前的
    SynchronizationContext
    AspNetSynchronizationContext
    ,而在
    AsyncContext
    中并非如此
  • 使用阻塞分离线程(使用
    Task.Run(…).GetAwaiter().GetResult()
    )。这种方法避免了您在阻塞时看到的死锁,但它确实在ASP.NET上下文之外执行代码。这种方法也会对您的可伸缩性产生负面影响(这就是在ASP.NET上首先使用
    async
    的要点)
  • 换句话说,这些只是黑客


    ASP.NET vNext具有“查看组件”的概念,它可能是异步的,因此这将是未来的自然解决方案。对于今天的代码,IMO的最佳解决方案是使方法同步;这比实施黑客攻击要好。

    谢谢大家的建议

    所有的“变通方法”都不起作用,我们需要保持现有的
    async
    代码,所以我决定不反对
    async

    我通过根本不使用
    @Html.Action
    避免了这个问题

    相反,我使用菜单作为局部视图,使用

    @Html.Partial("MainMenu", @ViewBag.MainMenuViewModel);
    

    并在我们的基本控制器中设置该模型。

    您需要制作一段实际复制您的问题的示例代码。我已经尝试了尽可能类似的方法,并且它可以正常工作。它也可能在您的
    应用程序同步
    方法中。将其缩减为实际显示问题的最简单代码段e不正确的行为。@Luaan:这都是标准的identity framework组件,因此我将用只返回0的更简单的组件替换它。为什么不使用basic
    var r=CandidateIdAsync().ConfigureAwait(false)。结果
    ?请注意System.Web和(类似的Web相关程序集)中的大多数in方法期望设置HttpContext.Current和Culture-显示如何在ASP.Net中同步运行异步方法的大多数方法都将在未设置HttpContext.Current的线程上运行方法(包括我建议的
    ConfigureAwait
    )。这就是问题所在-
    RunSync
    不会将异步方法同步回
    public class MenuController : Controller
    {
        readonly ICurrentCandidate _currentCandidate;
    
        public MenuController(ICurrentCandidate currentCandidate)
        {
            _currentCandidate = currentCandidate;
        }
    
        // GET: MainMenu
        public ActionResult MainMenu()
        {
            if (_currentCandidate == null)
            {
                throw new ArgumentNullException("_currentCandidate");
            }
            var candidateId = AsyncHelper.RunSync<int>(() => _currentCandidate.CandidateIdAsync());
    
            [snip]            
            return View(vm);
        }
    }
    
    // This works
    public async Task<int> CandidateIdAsync()
    {
        return 0;
    }
    
    public class CurrentCandidate : ICurrentCandidate
    {
        private readonly ApplicationDbContext _applicationDbContext;
        private readonly IApplicationUserManager _userManager;
        private readonly ICandidateStore _candidateStore;
    
        public CurrentCandidate(ApplicationDbContext applicationDbContext, ICandidateStore candidateStore, IApplicationUserManager userManager)
        {
            this._candidateStore = candidateStore;
            this._applicationDbContext = applicationDbContext;
            this._userManager = userManager;    // new ApplicationUserManager(new UserStore<ApplicationUser>(this._applicationDbContext));
        }
    
        public async Task<ApplicationUser> ApplicationUserAsync()
        {
            var applicationUser = await this._userManager.FindByIdAsync(HttpContext.Current.User.Identity.GetUserId());
            return applicationUser;
        }
    
        public bool IsAuthenticated()
        {
            return HttpContext.Current.User.Identity.IsAuthenticated;
        }
    
        public async Task<int> CandidateIdAsync()
        {
            var applicationUser = await this.ApplicationUserAsync();
            if (applicationUser != null)
            {
                return applicationUser.CandidateId.GetValueOrDefault();
            }
            return 0;
        }
    }
    
    var r = Task.Run( () => MyAsynchMethod(args)).Result;
    
    var task = MyAsynchMethod(args);
    task.ConfigureAwait(false);
    var r = task.Result;
    
     var context = HttpContext.Current;  
     var r = Task.Run( () => 
        {
           HttpContext.Current = context;
           return MyAsynchMethod(args);
        }
    ).Result;
    
    @Html.Partial("MainMenu", @ViewBag.MainMenuViewModel);