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菜单
controllerMenu
操作在这样注入时仍能工作(而不必移动到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上下文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的更简单的组件替换它。为什么不使用basicvar 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);