【问题标题】:providing an asynchronous programming model: Should I? and if so, should it be VerbAsync() or BeginVerb()?提供异步编程模型:我应该吗?如果是这样,它应该是 VerbAsync() 还是 BeginVerb()?
【发布时间】:2009-09-04 15:55:43
【问题描述】:

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

其中一个答案建议类库开发人员应尽可能避免提供基于single responsibility principle 的异步方法。

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

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

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

??

“事件驱动模型”是一种,如果方法的同步版本是 Verb(),则方法的异步版本是 VerbAsync() ,并且有一个动词完成的事件。
IAsyncResult 模式是众所周知的 BeginVerb() 和 EndVerb() 方法。

【问题讨论】:

    标签: .net asynchronous class-design


    【解决方案1】:

    您为什么考虑提供异步 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,我建议您通过“经典开始/结束”IAsyncResult API 提供它。原因是,据我记忆,它应该与 .NET 4.0 的任务并行库更好地集成。

    【讨论】:

      【解决方案2】:
      1. 我不同意。如果你知道你在做什么。使用它!

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

      基于事件的异步模式更容易被新手开发者理解,并且在 IDE 上有更好的支持。

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

      【讨论】:

      • Re: 1) 有很多简单的方法可以将工作推送到 .NET 中的另一个线程,我认为应该避免异步组件编写,除非对象有特定、明确的原因来实现它.
      • 这正是我想要解决的问题。是口味问题还是有明确的指导?
      【解决方案3】:

      你是否提供这个真的有点取决于你的方法要做什么。如果在没有这个的情况下以异步方式使用您的对象很容易,那么我会避免在您的对象中实现异步方法。

      例如,当 .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
      

      【讨论】:

      • 哈。好的,“足够容易使用”是什么意思?一个人写 Action a = instance.Method 有多难? a.BeginInvoke(MyCallback, null); ??对我来说这似乎并不难,但有些人要求使用异步模型。我已经实现了它,但现在我担心代码膨胀和关注点分离。
      • 我将更新我的答案,以便更明确地说明我将如何处理这个问题。
      猜你喜欢
      • 2019-03-04
      • 1970-01-01
      • 1970-01-01
      • 2020-11-30
      • 2014-11-06
      • 2012-07-23
      • 1970-01-01
      • 1970-01-01
      • 2014-12-08
      相关资源
      最近更新 更多