由于您无法通过子窗口(@Html.Action
)调用运行异步方法,因此我一直在寻找从非异步方法运行异步任务的最简单方法。这样,我的MainMenu
控制器Menu
动作在这样注入时仍然可以工作(而不是必须移至VM或Ajax解决方案):
<div class="row">
@Html.Action("MainMenu", "Menu")
</div>
我使用MS自己使用的代码副本尝试了这种有前途的方法:How to call asynchronous method from synchronous method in C#?
AsyncHelper代码:
public 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)
{
return AsyncHelper._myTaskFactory
.StartNew<Task<TResult>>(func)
.Unwrap<TResult>()
.GetAwaiter()
.GetResult();
}
public static void RunSync(Func<Task> func)
{
AsyncHelper._myTaskFactory
.StartNew<Task>(func)
.Unwrap()
.GetAwaiter()
.GetResult();
}
}
我正在像这样消费它:
public async Task<ActionResult> MainMenu()
{
if (_currentCandidate == null)
{
throw new ArgumentNullException("_currentCandidate");
}
var candidateId = AsyncHelper.RunSync<int>(() => _currentCandidate.CandidateIdAsync());
[snip]
}
哪个调用此异步方法:
public async Task<int> CandidateIdAsync()
{
var applicationUser = await this.ApplicationUserAsync();
if (applicationUser != null)
{
return applicationUser.CandidateId.GetValueOrDefault();
}
return 0;
}
仅当我运行此命令时,才会出现以下错误:
我在这里想念什么?该代码看起来应该可以工作,但是我对它还不很熟悉,因此无法弄清楚。
更新:
作为参考,
MainMenu
控制器类如下所示: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);
}
}
另一个更新:
由于简化的
CandidateIdAsnyc
起作用,因此故障似乎在相关的IF代码内部:// 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;
}
}
最佳答案
我一直在寻找从非异步方法运行异步任务的最简单方法。
已经对此进行了多次讨论,并且没有适用于每种情况的解决方案。内部AsyncHelper
类型仅在ASP.NET团队知道其安全的非常特殊的情况下使用;这不是通用解决方案。
通常,这些方法是:
阻止(使用Result
或GetAwaiter().GetResult()
)。除非您始终使用ConfigureAwait(false)
-除非您始终使用ConfigureAwait(false)
-并且您调用的所有代码也始终使用ConfigureAwait(false)
,否则此方法can cause deadlocks(正如我在博客中所描述的)。但是,请注意,除非您的代码实际上不需要ASP.NET上下文,否则它不能使用AsyncContext
。
嵌套消息循环。可以使用AsyncEx库中的SynchronizationContext
之类的东西来实现。但是,有许多ASP.NET API隐式地假定当前AspNetSynchronizationContext
是AsyncContext
,而在Task.Run(...).GetAwaiter().GetResult()
中则不是这种情况。
带阻塞的单独线程(使用async
)。这种方法避免了仅通过阻塞就可以看到的死锁,但是它确实在ASP.NET上下文之外执行代码。这种方法也会对您的可伸缩性产生负面影响(首先是在ASP.NET上使用async
的全部要点)。
换句话说,这些只是黑客。
ASP.NET vNext具有“查看组件”的概念,该概念可能是,因此,这将是将来的自然解决方案。对于当今的代码,IMO最好的解决方案是使方法同步。这比实施黑客更好。