.net c#尝试异步方法失败

Attempt at an Asynchronous method is failing
2021-07-31
  •  译文(汉语)
  •  原文(英语)

我有一个使用实体框架(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应用

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

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的创建和电子邮件消息分成了其他类别),但是所发生的事情是以上!

我的问题是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. 我应该将新的unitOfWork实例提供给Notify方法吗?

  4. 我是否使用正确的模式/类来异步调用该方法?还是我应该按照...的方式使用某些东西?

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

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

  5. 任何链接到文章(白痴的C#异步)将不胜感激!

非常感谢.

更新:

进一步的挖掘使我进入了此SO页面:https : //stackoverflow.com/a/5491978/192999,其中说...

"请注意,尽管EF上下文不是线程安全的,即您不能在多个线程中使用同一上下文."

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

速聊1:
我建议创建另一个Service类,然后在其中移动您的Notify方法.在该新服务中,请确保您具有一个构造函数,该构造函数将使用与主线程中使用的DbContext不同的DbContext创建一个新的unitOfWork.或者更好的是,将所有与数据库相关的调用移到主线程中,并使派生线程DB保持免调用状态.
速聊2:
谢谢,我只是按照您的建议进行了操作,并产生了一个新的unitOfWork,而且一切似乎都正常了.我现在要整理构造函数中的内容.回复:将所有数据库调用保留在主线程上.那意味着用户必须等待所有的数据库调用才能完成,这是我想避免的.
速聊3:
很高兴您能正常使用.如果数据库调用花费的时间也很长(我虽然只是创建PDF),那么可以移动它们.
速聊4:
这可能不完全是您要查找的内容,但是EF6(最近发布了beta版)支持.NET 4.5新增的async / await关键字.odetocode.com/blogs/scott/archive/2012/08/28/…
速聊5:
难道是矫枉过正推荐particular.net/nservicebus?
解决过程1

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

如果请求"丢失"了,那么如果您的应用程序在请求文档之后,生成/发送之前崩溃的情况下也很糟糕,那么您也可以使用"同步队列".

正如rikitikitik所说,几乎可以肯定的是 ,您将需要使用一个新的工作单元,这意味着需要进行单独的交易.

您还可以查看最佳线程队列示例/最佳实践.

I have an MVC3/.NET 4 application which uses Entity Framework (4.3.1 Code First)

I have wrapped EF into a Repository/UnitOfWork pattern as described here…

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

Typically, as it explains in the article, when I require the creation of a new record I’ve been doing this…

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

However, when more than simply saving a record to a database is required I wrap the logic into what I’ve called an IService. For example…

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

In one of my services I have something like the following…

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

The above isn’t the actual code (my app has nothing to do with courses, I’m using view models, and I have separated the PDF creation and email message out into other classes) but the gist of what is going on is as above!

My problem is that the generation of the PDF and emailing it out is taking some time. The user just needs to know that the record has been saved to the database so I thought I would put the code below the unitOfWork.Save(); into an asynchronous method. The user can then be redirected and the server can happily take its time processing the emails, and attachments and whatever else I require it to do post save.

This is where I’m struggling.

I’ve tried a few things, the current being the following in 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.   
    }
}

My Questions/Problems

I’m randomly getting the error: "There is already an open DataReader associated with this Command which must be closed first." in the Notify() method.

  1. Is it something to do with the fact that I’m trying to share the unitOrWork and therefore a dbContext across threads?

  2. If so, can someone be kind enough to explain why this is a problem?

  3. Should I be giving a new instance of unitOfWork to the Notify method?

  4. Am I using the right patterns/classes to invoke the method asynchronously? Or should I be using something along the lines of....

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

    I must say I've become very confused with the terms asynchronous, parallel, and concurrent!!

  5. Any links to articles (c# async for idiots) would be appreciated!!

Many thanks.

UPDATE:

A little more digging got me to this SO page: https://stackoverflow.com/a/5491978/192999 which says...

"Be aware though that EF contexts are not thread safe, i.e. you cannot use the same context in more than one thread."

...so am I trying to achieve the impossible? Does this mean I should be creating a new IUnitOfWork instance for my new thread?

Talk1:
I suggest creating another Service class and move your Notify method there. In that new Service, make sure that you have a constructor that would create a new unitOfWork with a different DbContext than the one you used in the main thread. Or better yet, move all database-related calls in your main thread and keep the spawned thread DB call-free.
Talk2:
Thanks, I was just doing as you've suggested, and have spawned a new unitOfWork, and all seems to be working. I've just got to tidy things up now in the constructors. Re: leaving all DB calls on the main thread. That would mean the user having to wait on all the db calls to finish, which I want to avoid.
Talk3:
Glad that you had it working. If the DB calls are taking a long time as well (I though it was just the PDF creation), then yeah, move them.
Talk4:
This may not be exactly what you are looking for, but EF6 (which recently hit beta) supports the async/await keywords that are new to .NET 4.5. odetocode.com/blogs/scott/archive/2012/08/28/…
Talk5:
Would it be overkill to recommend particular.net/nservicebus ?
Solutions1

You could create a polling background thread that does the lengthy operation separately from your main flow. This thread could scan the database for new items (or items marked to process). This solution is pretty simple and ensures that jobs get done even if you application crashes (it will be picked up when the polling thread is started again).

You could also use a Synchronised Queue if it's not terrible if the request is 'lost', in the case your application crashes after the doc is requested and before it's generated/sent.

One thing is almost sure - as rikitikitik said - you will need to use a new unit of work, which means a separate transaction.

You could also look at Best threading queue example / best practice .

转载于:https://stackoverflow.com/questions/17070415/attempt-at-an-asynchronous-method-is-failing

本人是.net程序员,因为英语不行,使用工具翻译,希望对有需要的人有所帮助
如果本文质量不好,还请谅解,毕竟这些操作还是比较费时的,英语较好的可以看原文

留言回复
我们只提供高质量资源,素材,源码,坚持 下了就能用 原则,让客户花了钱觉得值
上班时间 : 周一至周五9:00-17:30 期待您的加入