代码之家  ›  专栏  ›  技术社区  ›  Cheeso

提供异步编程模型:我应该吗?如果是,它应该是verbasync()还是beginverb()?

  •  5
  • Cheeso  · 技术社区  · 15 年前

    Providing Synchronous and Asynchronous versions of Method in c# 怎样 提供方法的异步版本。

    其中一个答案建议类库开发人员应避免在可能的情况下基于 single responsibility principle .

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

    2. 如果答案是 (换句话说, 提供方法的异步版本),那么我应该按照 this MSDN article ,这说明:

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

    ??

    如果方法的同步版本是 动词() ,方法的异步版本为 动词 async(),有一个 动词 已完成事件。
    IAsyncResult模式是众所周知的开始 动词 ()和结束 动词 ()方法。

    3 回复  |  直到 15 年前
        1
  •  6
  •   Phil    15 年前

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

    API操作是IO绑定的还是CPU绑定的?(是因为等待IO完成需要很长时间,还是因为它占用大量CPU?)

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

    提供异步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

    希望这有帮助, Phil。

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

        2
  •  2
  •   Alfred Myers    15 年前
    1. 我不同意。如果你 知道 你在做什么。用它!

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

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

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

        3
  •  1
  •   Reed Copsey    15 年前

    您是否提供这一点真正取决于您的方法将要做什么。如果在没有这个的情况下以异步方式使用对象足够简单,那么我将避免在对象中实现异步方法。

    例如,当.NET 4被释放时,任务并行库将使异步使用同步对象成为一项微不足道的“任务”,这意味着只需编写一个版本的软件就很容易,并允许异步或同步使用它。今天使用threadpool相当容易。

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


    如果你看 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