异步编程和Contination
- Task非常适合异步编程,因为它们支持Contination(它对异步非常重要)
- 之前TaskCompletionSource的例子。
- TaskCompletionSource是实现底层IO-bound异步方法的一种标准方式
- 对于Compute-bound方法,Task.Run会初始化绑定线程的并发。
- 把task返回调用者,创建异步方法;
- 异步编程的区别:目标是在调用图较低的位置来这样做。
- 富客户端应用中,高级方法可以保留在UI线程和访问控制以及共享状态上,不会出现线程安全问题。
同步方法
class Program{static void Main(string[] args){DisplayPrimeCounts();}static void DisplayPrimeCounts(){for (int i = 0; i < 10; i++){Console.WriteLine(GetPrimesCount(i * 1000000 + 2, 1000000) + " primes between " + (i * 1000000) +" and " + ((i + 1) * 1000000 - 1));}Console.WriteLine("Done!");}static int GetPrimesCount(int start, int count){return ParallelEnumerable.Range(start, count).Count(n => Enumerable.Range(2, (int) Math.Sqrt(n) - 1).All(i => n % i > 0));}}
异步写法(粗粒度)
// 将上面调用的地方改成Task.Run(() => DisplayPrimeCounts());Console.ReadKey();// 这种写法颗粒度较高
颗粒度适中的写法(因为是并行执行的,结果可能并不是我们想要的)
class Program{static void Main(string[] args){DisplayPrimeCounts();//Task.Run(() => DisplayPrimeCounts());Console.ReadKey();}static void DisplayPrimeCounts(){//for (int i = 0; i < 10; i++)//{// Console.WriteLine(GetPrimesCount(i * 1000000 + 2, 1000000) + " primes between " + (i * 1000000) +// " and " + ((i + 1) * 1000000 - 1));//}//Console.WriteLine("Done!");for (int i = 0; i < 10; i++){var awaiter = GetPrimesCountAsync(i * 1000000 + 2, 1000000).GetAwaiter();var temp = i;awaiter.OnCompleted(() =>{Console.WriteLine(awaiter.GetResult() + " primes between... " + (temp * 1000000) +" and " + ((temp + 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)));}}// Result 可以看到,结果先输出Done 且无序Done64336 primes between... 5000000 and 599999970435 primes between... 1000000 and 199999963129 primes between... 7000000 and 799999965367 primes between... 4000000 and 499999978498 primes between... 0 and 99999966330 primes between... 3000000 and 399999963799 primes between... 6000000 and 699999967883 primes between... 2000000 and 299999962712 primes between... 8000000 and 899999962090 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)的基础。
