为什么我不能将异步查询移动到方法中?

时间:2014-02-21 16:12:51

标签: c# asynchronous async-await c#-5.0

以下代码可以使用

[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();
}

它没有给我以下错误

  

第二个操作在此前一个上下文之前开始   异步操作完成。使用'await'确保任何   在调用另一个方法之前已完成异步操作   在这种背景下。任何实例成员都不能保证是线程   安全

这是为什么?我怎样才能做到这一点?

更新

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);
        }

3 个答案:

答案 0 :(得分:2)

使用最新的EF 6.1.0 Beta尝试使用您的代码。目前的EF6 definition of thread safety有点模糊:

  

线程安全

     

虽然线程安全会使异步更有用,但它是正交的   特征。目前尚不清楚我们是否可以实现对它的支持   考虑到EF与组成的图形交互,最常见的情况   用户代码维护状态并没有简单的方法来确保   这段代码也是线程安全的。

     

目前,EF将检测开发人员是否尝试执行   一次两个异步操作并抛出。

您的代码看起来不会同时执行两个以上的异步操作 ,但在ASP.NET中,线程切换可能并且确实在await个延续之间进行。理论上,EF6仍然支持这种情况。但是,由于ASP.NET中缺少线程关联性而导致消除EF6错误的可能性,您可以尝试the related question中的ThreadWithAffinityContext,如下所示:

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包含一个使用示例。

答案 1 :(得分:0)

以下测试代码工作得很好。显示.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 );
    }
}

答案 2 :(得分:-2)

你错过了函数中的async await,它应该是:

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();
}

如果没有它们,它将同时在单独的线程中运行两个调用,同时导致两次到达上下文。