同步 VS 异步

  • 同步操作会在返回调用者之前完成它的工作
  • 异步操作会在返回调用者之后去做它的(大部分)工作
    • 异步的方法更为少见,会启用并发,因为它的工作会与调用者并行执行。
    • 异步方法通常很快(立即)就会返回调用者,所以叫非阻塞方法。
  • 目前见到的大部分的异步方法都是通用目的的:
    • Thread.Start
    • Task.Run
    • 可以将continuation附加到Task的方法上

什么是异步编程

  • 异步编程的原则是将长时间运行的函数写成异步的。
  • 传统的做法是将长时间运行的函数写成同步的,然后从新的线程或Task进行调用,从而按需引入并发。
  • 上述异步方式的不同之处在于,它是从长时间运行函数的内部启动并发的(和传统的是不一样的)。这有两点好处:
    • IO-Bound并发可不使用线程来实现(比如说回调)。可提高可扩展性和执行效率(因为不需要开辟新的线程);
    • 富客户端应用在worker线程会使用更少的代码,简化了线程安全性。

异步编程的两种用途

  • 编写高效处理大量并发IO的应用程序(典型的:服务器端应用)
    • 它们的挑战并不是线程安全(因为共享状态通常是最小化的),而是执行效率。
      • 特别的,web应用程序,每个网络请求并不会消耗一个线程。
  • 富客户端应用程序,调用图(call graph)
  • image.png
  • 在富客户端应用里简化线程安全。
    • 如果调用图中任何一个操作是长时间运行的,那么整个call graph必须运行在worker线程上,以保证UI响应(因为在主线程上执行长时间操作,也就是UI,会没有响应,处于假死的状态)。
      • 得到一个横跨多个方法的单一并发操作(粗颗粒);
      • 同时,需要为call graph中的每个方法考虑线程安全。
    • 异步的call graph,直到需要的时候才开启一个线程,通常比较浅(IO-bound操作完全不需要线程了,会使用回调等方式)
      • 其它的方法可以在UI线程执行,线程安全简化。
      • 并发的颗粒度适中:
        • 一连串的并发操作,操作之间会弹回到UI线程。

经验之谈

  • 为了获得上述好处,下列操作建议异步编写:
    • IO-bound和Compute-bound操作
    • 执行超过50毫秒的操作
  • 另一方面过细的颗粒度会损害性能,因为异步操作也有开销。