异步编程和Contination

  • Task非常适合异步编程,因为它们支持Contination(它对异步非常重要)
    • 之前TaskCompletionSource的例子。
    • TaskCompletionSource是实现底层IO-bound异步方法的一种标准方式
  • 对于Compute-bound方法,Task.Run会初始化绑定线程的并发。
    • 把task返回调用者,创建异步方法;
    • 异步编程的区别:目标是在调用图较低的位置来这样做。
      • 富客户端应用中,高级方法可以保留在UI线程和访问控制以及共享状态上,不会出现线程安全问题。
  • 同步方法

    1. class Program
    2. {
    3. static void Main(string[] args)
    4. {
    5. DisplayPrimeCounts();
    6. }
    7. static void DisplayPrimeCounts()
    8. {
    9. for (int i = 0; i < 10; i++)
    10. {
    11. Console.WriteLine(GetPrimesCount(i * 1000000 + 2, 1000000) + " primes between " + (i * 1000000) +
    12. " and " + ((i + 1) * 1000000 - 1));
    13. }
    14. Console.WriteLine("Done!");
    15. }
    16. static int GetPrimesCount(int start, int count)
    17. {
    18. return ParallelEnumerable.Range(start, count)
    19. .Count(n => Enumerable.Range(2, (int) Math.Sqrt(n) - 1).All(i => n % i > 0));
    20. }
    21. }
  • 异步写法(粗粒度)

    1. // 将上面调用的地方改成
    2. Task.Run(() => DisplayPrimeCounts());
    3. Console.ReadKey();
    4. // 这种写法颗粒度较高
  • 颗粒度适中的写法(因为是并行执行的,结果可能并不是我们想要的)

    1. class Program
    2. {
    3. static void Main(string[] args)
    4. {
    5. DisplayPrimeCounts();
    6. //Task.Run(() => DisplayPrimeCounts());
    7. Console.ReadKey();
    8. }
    9. static void DisplayPrimeCounts()
    10. {
    11. //for (int i = 0; i < 10; i++)
    12. //{
    13. // Console.WriteLine(GetPrimesCount(i * 1000000 + 2, 1000000) + " primes between " + (i * 1000000) +
    14. // " and " + ((i + 1) * 1000000 - 1));
    15. //}
    16. //Console.WriteLine("Done!");
    17. for (int i = 0; i < 10; i++)
    18. {
    19. var awaiter = GetPrimesCountAsync(i * 1000000 + 2, 1000000).GetAwaiter();
    20. var temp = i;
    21. awaiter.OnCompleted(() =>
    22. {
    23. Console.WriteLine(awaiter.GetResult() + " primes between... " + (temp * 1000000) +
    24. " and " + ((temp + 1) * 1000000 - 1));
    25. });
    26. }
    27. Console.WriteLine("Done");
    28. }
    29. static Task<int> GetPrimesCountAsync(int start, int count)
    30. {
    31. return Task.Run(() => ParallelEnumerable.Range(start, count)
    32. .Count(n => Enumerable.Range(2, (int) Math.Sqrt(n) - 1).All(i => n % i > 0)));
    33. }
    34. }
    35. // Result 可以看到,结果先输出Done 且无序
    36. Done
    37. 64336 primes between... 5000000 and 5999999
    38. 70435 primes between... 1000000 and 1999999
    39. 63129 primes between... 7000000 and 7999999
    40. 65367 primes between... 4000000 and 4999999
    41. 78498 primes between... 0 and 999999
    42. 66330 primes between... 3000000 and 3999999
    43. 63799 primes between... 6000000 and 6999999
    44. 67883 primes between... 2000000 and 2999999
    45. 62712 primes between... 8000000 and 8999999
    46. 62090 primes between... 9000000 and 9999999

语言对异步的支持非常重要

  • 我们需要对task的执行序列化(按顺序执行)

    • 例如Task B 依赖于 Task A 的执行结果。
    • 例子(下面),必须在continuation内部触发下一次循环。

      class Program
      {
         static void Main(string[] args)
         {
             DisplayPrimeCountsAsync();
             Console.ReadKey();
         }
         static Task DisplayPrimeCountsAsync()
         {
             var machine = new PrimesStateMachine();
             machine.DisplayPrimeCountsFrom(0);
             return machine.Task;
         }
         public static Task<int> GetPrimesCountAsync(int start, int count)
         {
             return Task.Run(() => ParallelEnumerable.Range(start, count)
                 .Count(n => Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i => n % i > 0)));
         }
      }
      
      class PrimesStateMachine
      {
         TaskCompletionSource<object> _tcs = new TaskCompletionSource<object>();
         public Task Task => _tcs.Task;
         public void DisplayPrimeCountsFrom(int i)
         {
             var awaiter = Program.GetPrimesCountAsync(i * 1000000 + 2, 1000000).GetAwaiter();
             awaiter.OnCompleted(() =>
             {
                 Console.WriteLine(awaiter.GetResult() + " primes between... " + (i * 1000000) +
                                   " and " + ((i + 1) * 1000000 - 1));
                 if (++i < 10)
                 {
                     DisplayPrimeCountsFrom(i);
                 }
                 else
                 {
                     Console.WriteLine("Done");
                     _tcs.SetResult(null);
                 }
             });
         }
      }
      
  • async await

      class Program
      {
          static async Task Main(string[] args)
          {
              await DisplayPrimeCounts();
              Console.ReadKey();
          }
          static async Task DisplayPrimeCounts()
          {
              for (int i = 0; i < 10; i++)
              {
                  Console.WriteLine(await GetPrimesCountAsync(i * 1000000 + 2, 1000000) + " primes between " + (i * 1000000) +
                                    " and " + ((i + 1) * 1000000 - 1));
              }
              Console.WriteLine("Done");
          }
          static Task<int> GetPrimesCountAsync(int start, int count)
          {
              return Task.Run(() => ParallelEnumerable.Range(start, count)
                  .Count(n => Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i => n % i > 0)));
          }
      }
    
    • 对于不想复杂实现的实现异步非常重要。
  • 命令式循环结构for循环和foreach)不要和continuation混合在一起,因为它们依赖于当前本地状态。
  • 另一种实现,函数式写法(Linq查询),它也是响应式编程(Rx)的基础。