为啥我不能将异步查询移动到方法中?
Posted
技术标签:
【中文标题】为啥我不能将异步查询移动到方法中?【英文标题】:Why can't I move async query into method?为什么我不能将异步查询移动到方法中? 【发布时间】:2014-03-23 06:22:15 【问题描述】:以下代码有效
[Route("case-studies/slug")]
public async Task<ActionResult> Details(string slug)
var item = await Db.Pages.OfType<CaseStudy>()
.WithSlug(slug)
.FirstOrDefaultAsync();
if (item == null)
return HttpNotFound();
var related = await Db.Pages.OfType<CaseStudy>()
.Where(r => r.Client == item.Client && r.Id != item.Id)
.Where(r => !r.IsArchived)
.Include(r => r.Media)
.Take(3)
.Project()
.To<RelatedPageModel>()
.ToListAsync();
var archived = await Db.Pages.OfType<CaseStudy>()
.Where(r => r.Client == item.Client && r.Id != item.Id)
.Where(r => r.IsArchived)
.Take(3)
.Project()
.To<RelatedPageModel>()
.ToListAsync();
ViewData.Model = new DetailPageModel<CaseStudy>()
Item = item,
RelatedItems = related,
ArchivedItems = archived
;
return View();
但是,当我尝试如下重构异步方法调用时
[Route("case-studies/slug")]
public async Task<ActionResult> Details(string slug)
var item = await Db.Pages.OfType<CaseStudy>()
.WithSlug(slug)
.FirstOrDefaultAsync();
if (item == null)
return HttpNotFound();
var related = await GetRelatedCaseStudies(item, false);
var archived = await GetRelatedCaseStudies(item, true);
ViewData.Model = new DetailPageModel<CaseStudy>()
Item = item,
RelatedItems = related,
ArchivedItems = archived
;
return View();
private Task<List<RelatedPageModel>> GetRelatedCaseStudies(CaseStudy casestudy, bool archived)
return Db.Pages.OfType<CaseStudy>()
.Where(r => r.Client == casestudy.Client && r.Id != casestudy.Id)
.Where(x => x.IsArchived == archived)
.Include(r => r.Media)
.Take(3)
.Project().To<RelatedPageModel>()
.ToListAsync();
它没有给我以下错误
第二个操作在前一个上下文之前开始 异步操作完成。使用“等待”确保任何 异步操作在调用另一个方法之前已经完成 在这种情况下。不保证任何实例成员都是线程 安全。
这是为什么?我怎样才能做到这一点?
更新:
Db 在基本控制器中声明如下
private WebSiteDb db;
protected WebSiteDb Db
get
LazyInitializer.EnsureInitialized(ref db, () => new WebSiteDb());
return db;
WebSiteDb 扩展 DbContext 如下
[DbConfigurationType(typeof(DbConfig))]
public class WebSiteDb : DbContext
static WebSiteDb()
Database.SetInitializer<WebSiteDb>(new WebSiteDbInitializer());
public IDbSet<Page> Pages get; set;
public IDbSet<Media> Media get; set;
...some missing sets
public WebSiteDb() : base("MyDatabaseName")
如果我在方法内部等待,则会从方法内部抛出错误
WithSlug()如下
public static IQueryable<T> WithSlug<T>(this IQueryable<T> pages, string slug) where T : Page
return pages.Where(p => p.Slug == slug);
【问题讨论】:
知道它在哪一行失败了吗? @MarcGravell 第一个 await 方法调用 - var related = await GetRelatedCaseStudies(item, false); 什么是Db
?它是如何声明的?
@StephenCleary 已更新问题
Project()
方法的定义是什么?这是您添加的扩展程序还是其他内容的一部分?
【参考方案1】:
使用最新的EF 6.1.0 Beta 试用您的代码。现在的EF6 definition of thread safety有点模糊:
线程安全
虽然线程安全会使异步更有用,但它是正交的 特征。目前尚不清楚我们是否可以在 最一般的情况,假设 EF 与组成的图交互 用户代码来维护状态并且没有简单的方法来确保 这段代码也是线程安全的。
目前,EF 将检测开发人员是否尝试执行 一次执行两个异步操作并抛出。
您的代码似乎不会同时执行两个以上的异步操作,但在 ASP.NET 中,线程切换可能而且确实发生在 await
延续之间。理论上,EF6 应该仍然支持这种场景。但是,要消除 ASP.NET 中缺少线程亲和性导致的 EF6 错误的可能性,您可以试试我的 ThreadWithAffinityContext
from the related question,如下所示:
public async Task<ActionResult> Details(string slug)
Func<Task<ActionResult>> doAsync = async () =>
var item = await Db.Pages.OfType<CaseStudy>()
.WithSlug(slug)
.FirstOrDefaultAsync();
if (item == null)
return HttpNotFound();
var related = await Db.Pages.OfType<CaseStudy>()
.Where(r => r.Client == item.Client && r.Id != item.Id)
.Where(r => !r.IsArchived)
.Include(r => r.Media)
.Take(3)
.Project()
.To<RelatedPageModel>()
.ToListAsync();
var archived = await Db.Pages.OfType<CaseStudy>()
.Where(r => r.Client == item.Client && r.Id != item.Id)
.Where(r => r.IsArchived)
.Take(3)
.Project()
.To<RelatedPageModel>()
.ToListAsync();
ViewData.Model = new DetailPageModel<CaseStudy>()
Item = item,
RelatedItems = related,
ArchivedItems = archived
;
return View();
;
using (var staThread = new Noseratio.ThreadAffinity.ThreadWithAffinityContext(
staThread: false, pumpMessages: false))
return await staThread.Run(() => doAsync(), CancellationToken.None);
注意,这不是生产解决方案,但它可能有助于发现 EF6 中的错误。 如果有错误,您可以考虑使用另一个帮助程序类 ThreadAffinityTaskScheduler
,直到该错误在未来的 EF 版本中得到修复。 ThreadAffinityTaskScheduler
运行 ThreadWithAffinityContext
线程池,因此应该比上面的代码更好地扩展。 linked question 包含一个使用示例。
【讨论】:
那么 EF6 中是否存在错误? @gibbocool,我无法重现和确认这一点,但我目前没有参与任何实质性的 EF6 项目。【参考方案2】:下面的测试代码可以正常工作。展示.WithSlug( slug )
的实现
class Program
static void Main( string[] args )
using( var db = new TestEntities() )
db.EntityBs.Add( new EntityB()
EntityBId = 78
);
db.SaveChanges();
Task.WaitAll( Test( db ) );
var input = Console.ReadLine();
static Task<List<EntityB>> GetEntityBsAsync( TestEntities db )
return db.EntityBs.ToListAsync();
static async Task Test( TestEntities db )
var a0 = await GetEntityBsAsync( db );
var a1 = await GetEntityBsAsync( db );
【讨论】:
奇怪。注释掉除.ToListAsync
之外的所有IQueryable
方法调用,看看是否可行。如果是这样,开始添加另一个方法调用,直到它中断。
您的 a0 和 a1 将填充一个 Task> 类型对象,而不是您期望的 List您在函数中缺少异步等待,应该是:
private async Task<List<RelatedPageModel>> GetRelatedCaseStudies(CaseStudy casestudy, bool archived)
return await Db.Pages.OfType<CaseStudy>()
.Where(r => r.Client == casestudy.Client && r.Id != casestudy.Id)
.Where(x => x.IsArchived == archived)
.Include(r => r.Media)
.Take(3)
.Project().To<RelatedPageModel>()
.ToListAsync();
如果没有这些,它将同时在不同的线程中运行两个调用,从而同时对上下文造成两次命中。
【讨论】:
这完全等同于OP的代码。它没有什么不同,除了你不必要地启动了一个你不使用任何功能的状态机。以上是关于为啥我不能将异步查询移动到方法中?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 C++11 不能将不可复制的仿函数移动到 std::function?
Android:为啥我不能使用“Intent”移动到另一个Activity?
Pybind11:为啥来自 Python 的异步调用不能在 C++ 中正确执行回调?