【问题标题】:Attempt at an Asynchronous method is failing尝试异步方法失败
【发布时间】:2013-06-12 16:14:51
【问题描述】:

我有一个使用实体框架(4.3.1 代码优先)的 MVC3/.NET 4 应用程序

我已将 EF 包装成一个 Repository/UnitOfWork 模式,如此处所述……

http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application

通常,正如文章中解释的那样,当我需要创建新记录时,我一直在这样做......

public ActionResult Create(Course course)
{
   unitOfWork.CourseRepository.Add(course);
   unitOfWork.Save();
   return RedirectToAction("Index");
}

但是,当需要的不仅仅是将记录保存到数据库中时,我会将逻辑包装到我所谓的 IService 中。比如……

private ICourseService courseService;
public ActionResult Create(Course course)
{
   courseService.ProcessNewCourse(course);
   return RedirectToAction("Index");
}

在我的一项服务中,我有类似以下的内容……

public void ProcessNewCourse(Course course)
{
    // Save the course to the database…
    unitOfWork.CourseRepository.Add(course);
    unitOfWork.Save();

    // Generate a PDF that email some people about the new course being created, which requires more use of the unitOfWork…
    var someInformation = unitOfWork.AnotherRepository.GetStuff();
    var myPdfCreator = new PdfCreator();

    IEnumerable<People> people = unitOfWork.PeopleRepository.GetAllThatWantNotifiying(course);

    foreach(var person in people)
    {
        var message = “Hi ” + person.FullName;
        var attachment = myPdfCreator.CreatePdf();
        etc...
        smtpClient.Send();        
    }
}

上面不是实际的代码(我的应用程序与课程无关,我使用的是视图模型,并且我已将 PDF 创建和电子邮件消息分离到其他类中),而是发生了什么的要点on 同上!

我的问题是生成 PDF 并通过电子邮件发送它需要一些时间。用户只需要知道记录已保存到数据库中,所以我想我会将代码放在 unitOfWork.Save();进入异步方法。然后可以重定向用户,服务器可以愉快地花时间处理电子邮件、附件以及我需要它做的其他任何事情。

这就是我苦苦挣扎的地方。

我已经尝试了一些东西,目前是 ICourseService 中的以下内容……

public class CourseService : ICourseService
{

    private delegate void NotifyDelegate(Course course);
    private NotifyDelegate notifyDelegate;

    public CourseService()
    {
        notifyDelegate = new NotifyDelegate(this.Notify);
    }

    public void ProcessNewCourse(Course course)
    {
        // Save the course to the database…
        unitOfWork.CourseRepository.Add(course);
        unitOfWork.Save();

        notifyDelegate.BeginInvoke(course);
    }

    private void Notify(Course course)
    {
        // All the stuff under unitOfWork.Save(); moved here.   
    }
}

我的问题/问题

我随机收到错误消息:“已经有一个打开的 DataReader 与此命令关联,必须先关闭它。”在 Notify() 方法中。

  1. 这与我尝试共享 unitOrWork 并因此跨线程共享 dbContext 的事实有关吗?

  2. 如果是这样,有人可以解释为什么这是一个问题吗?

  3. 我应该为 Notify 方法提供一个新的 unitOfWork 实例吗?

  4. 我是否使用正确的模式/类来异步调用该方法?或者我应该使用类似......

    new System.Threading.Tasks.Task(() => { Notify(course); }).Start();

    我必须说我对异步、并行和并发这些术语感到非常困惑!

  5. 任何文章链接(c# async for idiots)将不胜感激!

非常感谢。

更新:

多一点挖掘让我进入了这个 SO 页面:https://stackoverflow.com/a/5491978/192999 上面写着......

“请注意,尽管 EF 上下文不是线程安全的,即您不能在多个线程中使用相同的上下文。”

...所以我是在努力实现不可能的目标吗?这是否意味着我应该为我的新线程创建一个新的 IUnitOfWork 实例?

【问题讨论】:

  • 我建议创建另一个 Service 类并将您的 Notify 方法移到那里。在该新服务中,确保您有一个构造函数,该构造函数将创建一个新的 unitOfWork,其 DbContext 与您在主线程中使用的不同。或者更好的是,在主线程中移动所有与数据库相关的调用,并保持生成的线程 DB 无调用。
  • @rikitikitik 谢谢,我只是按照你的建议做,并产生了一个新的 unitOfWork,一切似乎都在工作。我现在只需要在构造函数中整理一下。回复:将所有数据库调用留在主线程上。这意味着用户必须等待所有数据库调用完成,我想避免这种情况。
  • 很高兴你让它工作。如果数据库调用也需要很长时间(我虽然只是创建 PDF),那么是的,移动它们。
  • 这可能不是您正在寻找的内容,但 EF6(最近发布了测试版)支持 .NET 4.5 中新增的 async/await 关键字。 odetocode.com/blogs/scott/archive/2012/08/28/…
  • 推荐particular.net/nservicebus会不会有点矫枉过正?

标签: c# entity-framework asynchronous unit-of-work


【解决方案1】:

您可以创建一个轮询后台线程,该线程与您的主要流程分开执行冗长的操作。该线程可以扫描数据库中的新项目(或标记为要处理的项目)。这个解决方案非常简单,即使您的应用程序崩溃也能确保作业完成(当再次启动轮询线程时,它将被拾取)。

如果请求“丢失”并不可怕,您也可以使用Synchronised Queue,以防您的应用程序在请求文档之后和生成/发送之前崩溃。

几乎可以肯定的是——正如 rikitikitik 所说——您将需要使用一个新的工作单元,这意味着一个单独的事务。

你也可以看看Best threading queue example / best practice

【讨论】:

    猜你喜欢
    • 2018-09-10
    • 2012-07-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多