提供异步编程模型:我应该吗?如果是这样,它应该是VerbAsync()还是BeginVerb()?

时间:2009-09-03 22:44:13

标签: .net asynchronous class-design

Providing Synchronous and Asynchronous versions of Method in c#要求如何提供方法的异步版本。

其中一个答案表明,如果可能,类库开发人员应该根据single responsibility principle避免提供异步方法。

  1. 这是真的吗? 我不应该提供异步版本的方法吗?

  2. 如果答案是(换句话说, DO 提供方法的异步版本),那么我应该遵循this MSDN article中的建议吗,其中说明:

  3.   

    IAsyncResult设计模式允许各种编程模型,但学习起来更复杂,并提供大多数应用程序不需要的灵活性。在可能的情况下,类库设计者应该使用事件驱动模型实现异步方法。在某些情况下,库设计者还应该实现基于IAsyncResult的模型。

    ...

    “事件驱动模型”是指如果方法的同步版本是 Verb(),则该方法的异步版本为 Verb Async() ,并且有一个动词已完成的事件 IAsyncResult模式是众所周知的Begin Verb ()和End Verb ()方法。

3 个答案:

答案 0 :(得分:6)

为什么要考虑提供异步API?

API操作IO绑定还是CPU绑定? (这是因为等待IO完成需要很长时间,或者仅仅是CPU密集型的原因?)

如果CPU绑定,我会考虑是否真的有必要提供异步版本,因为调用者总是可以通过线程池有效地将同步操作转换为异步操作。

提供异步API的最有力理由是针对IO绑定的操作。不是捆绑线程以等待高延迟IO操作,而是优选使用异步IO。这尤其会影响系统的可扩展性。例如,如果您在单个同步IO操作上阻塞了服务器,那么问题就不是太多了 - 您只是占用了一个线程。但是,同时执行1000个相同的操作,并且你要占用1000个线程(如果内存为每个线程提供1MB的堆栈),只需等待几个CPU周期的操作完成。最终结果 - 你有一个空闲的CPU,但正在吸收资源。

因此,例如,如果您自己编写套接字库,那么您肯定希望提供异步IO操作,以便库的用户可以编写可伸缩的应用程序。

例如,如果您正在编写加密库,那么即使操作可能需要很长时间,也可能没有迫切需要提供异步API - 无论如何它都是CPU绑定的。如上所述,用户可以使其异步。

最后,如果您正在编写一个IO绑定系统,该系统使用提供异步IO的低级API(例如,使用套接字类的FTP客户端),您可能还需要考虑提供API的异步版本。但事情是这样做并不容易 - 如果您使用较低级别的API的异步功能,则只能提供可伸缩性优势。这很快就会导致简单的同步逻辑转变为极其复杂,难以调试的异步逻辑。主要问题是您之前通过局部变量进行简单访问的所有状态信息最终需要手动捕获,以便在下一个IO操作完成时,您的逻辑知道下一步该做什么。

提供一个异步API然后在内部调用同步IO操作是没有意义的(尽管我已经看到它完成了)。给出了可扩展性的假象,但......不是!

当.NET 4.0问世时,其中一些可能会变得更简单一些(虽然我认为从我看到它仍然会很棘手)。

与此同时,您可能想查看Jeffrey Richter的异步枚举器库,它可以帮助简化(稍微):

Jeffrey Richter on his async enumerator

Power threading library including async enumerator

希望这有帮助, 菲尔。

注意:如果您要实现异步API,我建议通过'classic begin / end'IAsyncResult API提供它。原因是,根据我的记忆,它应该与.NET 4.0的任务并行库集成得更好。

答案 1 :(得分:2)

  1. 我不同意。如果您知道您正在做什么。使用它!

  2. 框架设计指南建议您使用基于事件的异步模式 用于较低级别API的更高级API和经典异步模式。

  3. 新手开发人员更容易理解基于事件的异步模式,并且对IDE有更好的支持。

    另一方面,经典异步模式允许您执行更多软化的操作,例如启动多个异步操作并等待其中一个或所有操作完成。

答案 2 :(得分:1)

您是否提供此功能实际上取决于您的方法将采取的措施。如果在没有这个的情况下以异步方式使用对象很容易,那么我将避免在对象中实现异步方法。

例如,当.NET 4发布时,任务并行库将异步使用同步对象作为一个简单的“任务” - 这意味着它很容易编写一个版本的软件,并允许它异步使用或同步的。今天使用线程池相当容易。

但是,如果您的方法本质上是等待您控制之外的东西(即:网络),那么拥有异步方法至关重要。在这种情况下,我认为应该支持两个选项,包括Begin *()/ End *()和* Async()/ * Completed。这两种模式都有它们的用途,并且很容易实现它们。


如果你看一下MSDN page on Asynchronous Programming,那么有一个很好的线索可以帮助你决定这是否合理:

  

异步操作通常用于执行可能需要很长时间才能完成的任务

请注意“可能”这个词。在确定方法是否应该是异步时,这对我来说是关键。如果某个任务要等待其他内容,并且可能需要很长时间,或者可能立即返回,这是我无法控制的,那么它应该是异步。

另一方面,如果始终需要花费很长时间才能运行,我宁愿将其留给调用者以决定如何处理它。把它推到另一个线程上特别容易:

 ThreadPool.QueueUserWorkItem( (o) => { DoWork(); }, null);

但是,如果调用者已经在后台/单独的线程中工作,则更容易同步使用它。 .NET 4中的任务使其变得更加容易:

 var result = Task<ReturnType>.Factory.StartNew( () => DoWork() );
 // It'll run, and if you want to do EndInvoke, you can do result.Result