.NET并发编程-异步编程模型下

Posted 西少壁画

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了.NET并发编程-异步编程模型下相关的知识,希望对你有一定的参考价值。

写给普通:

祝福所有低调善良的技术人

本系列学习在.NET中的并发并行编程模式,实战技巧
程序员经常遇到产品上线后出现各种莫名其妙的问题,"],["span",{"bold":true,"data-type":"leaf"},"在我本地是好好的啊"],["span",{"data-type":"leaf"},",也成为程序员面对未知问题的第一反应。这种不容易复现的问题,无非就是硬件不一致和软件不一致,更多的问题出在软件环境上,用户量、 并发这种测试容易遗漏的点。"]]],["p",{"blockquote":false},["span",{"data-type":"text"},["span",{"data-type":"leaf"},""]]],["p",{"blockquote":false},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"为了保证编写的代码在不同的环境中出现一致的行为结果,通常就要利用不可变的数据结构。数据一旦创建后就不能修改其本身,修改后会产生新的数据。"]]],["h4",{"blockquote":false},["span",{"data-type":"text"},["span",{"data-type":"leaf"},".NET不可变集合"]]],["p",{},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"在.NET4.5引入不可变集合,在命名空间System.Collecttions.Immutable中。(注意这个类库不是.net核心类库,需要从nuget上安装)。不可变的集合结构每次修改数据后都会生成新的集合。像String类型一样,对它Substring,Replace都会生成新的字符串。"]]],["p",{},["span",{"data-type":"text"},["span",{"data-type":"leaf"},""]]],["code",{"syntax":"plaintext","theme":"default","height":null,"id":"1tm6fv"},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"//可以将普通可变集合直接转为不可变集合\nvar dic = new Dictionary().ToImmutableDictionary();\n//直接创建不可变集合\nvar list = ImmutableList.Create(); \n list = list.Add(1); \n list = list.Add(2);\n list = list.Add(3); "]]],["p",{},["span",{"data-type":"text"},["span",{"data-type":"leaf"},""]]],["p",{},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"由于集合不可变,也就保证了多线程的安全。直接将集合丢给每个线程,原始集合不会变化。"]]],["h4",{},["span",{"data-type":"text"},["span",{"data-type":"leaf"},".NET并发集合"]]],["p",{},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"还有一种线程安全集合在System.Collections.Concurrent中,在多线程环境中建议使用此类集合。Concurrent集合是可变集合,但提供了细粒度和无锁模式来提高多线程应用程序的性能和可扩展性。像ConcurrentDictionary字典,除了像传统字典Dictionary使用,还提供了很多兼容并发的方法,如AddOrUpdate或GetOrAdd等。如果不使用并发集合,在多线程环境中我们需要设置锁来保证数据的一致性。"]]],["p",{},["span",{"data-type":"text"},["span",{"data-type":"leaf"},""]]],["h4",{},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"函数式数据结构"]]],["p",{},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"可持久化数据结构也称之为函数式数据结构。可持久化意味着数据结构是不可变的,修改只会返回修改后的新数据结构。(这里数据持久化和IO持久化区分)。大多数命令式数据结构都是短暂的,修改就破坏其结构。如Dictionary,List,Queue等。"]]],["p",{},["span",{"data-type":"text"},["span",{"data-type":"leaf"},""]]],["p",{},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"不可变性可能会带来一定的损耗,每次修改都会生成新的数据数据结构。但在托管编程语言中,如C# 和Java中,已经做了足够多的优化,且在多核时代,基本可以忽略性能的影响。"]]],["p",{},["span",{"data-type":"text"},["span",{"data-type":"leaf"},""]]],["p",{},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"以链表数据结构为例说明托管语言在共享数据结构上做的优化"]]],["p",{},["span",{"data-type":"text"},["span",{"data-type":"leaf"},""]],["img",{"id":"bambpp","name":"image.png","size":55723,"width":648,"height":287.4007134363853,"rotation":0,"src":"https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/a/d0Ngkv9ExU7yklk3/b63933b7bcb74b2d9c22f05f192718ca3943.png"},["span",{"data-type":"text"},["span",{"data-type":"leaf"},""]]],["span",{"data-type":"text"},["span",{"data-type":"leaf"},""]]],["p",{},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"不可变的数据集合,每次修改后,不是完整拷贝原集合,比如集合中追加一项,只会修改引用指向的位置,共享剩余其他结构。"]]],["p",{},["span",{"data-type":"text"},["span",{"data-type":"leaf"},""]],["img",{"id":"atbzas","name":"image.png","size":4302705,"width":648,"height":404.7096774193549,"rotation":0,"src":"https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/a/d0Ngkv9ExU7yklk3/57d60b3f33bb4d79aa7bac26d80731243943.png"},["span",{"data-type":"text"},["span",{"data-type":"leaf"},""]]],["span",{"data-type":"text"},["span",{"data-type":"leaf"},""]]],["p",{},["span",{"data-type":"text"},["span",{"data-type":"leaf"},""]]],["h4",{},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"设计一个不可变类"]]],["p",{},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"C#有readonly和const两个关键字,还记得他们的区别和用处吗。const静态常量,编译时被解析,通过类访问。readonly动态常量,可延迟到构造函数中初始化,通过类实例访问。"]]],["p",{},["span",{"data-type":"text"},["span",{"data-type":"leaf"},""]]],["code",{"syntax":"plaintext","theme":"default","height":null,"id":"4dgvgd"},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"public class Person\n{\n public const string Contry = \"中国\";\n public string Name { get; }\n public readonly Address Address;\n public Person(string name, Address address)\n {\n this.Name = name;\n this.Address = address;\n }\n}\n\npublic class Address\n{\n public string Street;\n public Address(string street)\n {\n this.Street = street;\n }\n}"]]],["p",{},["span",{"data-type":"text"},["span",{"data-type":"leaf"},""]]],["p",{},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"代码示例中,控制了Person的Address地址是不能被修改的,但它的底层字段Street仍然可以被修改。这就会导致person.Address.Street=\"M78星云\"这样的行为,所以这就是浅不可变。微软考虑到不可变编程的重要性,随后又在C#6.0又引入了自动属性的概念,可以轻松的创建一个不可变类。像示例中的public string Name { get; }这样。"]]]]">

本小节开始学习基于任务的函数式并行。本系列保证最少代码呈现量,虽然talk is cheap, show me the code被奉为圭臬,我的学习习惯是,只学习知识点,代码不在当下立马要用的时候不会认真去读的,更何况在大多时候在手机阅读更不顺畅。


上一小节介绍了.NET中的异步支持,以及任务异步模型,异步的取消等。本小节继续介绍下异步的重试和错误处理。


1、异步重试

异步请求有时会因为网络原因等到导致任务失败,重试也许就好了,因为对于任务可以指定次数的重试,两次尝试之间指定时间间隔。

public static async Task<T> Retry<T>(Func<Task<T>> task, int retries,TimeSpan delay, CancellationToken cts = default(CancellationToken)) => await task().ContinueWith(async innerTask => { cts.ThrowIfCancellationRequested(); if (innerTask.Status != TaskStatus.Faulted) return innerTask.Result; if (retries == 0) throw innerTask.Exception ?? throw new Exception(); await Task.Delay(delay, cts); return await Retry(task, retries - 1, delay, cts); }).Unwrap()

Retry异步操作,我们将此方法放在AsyncEx异步扩展静态类中。ThrowIfCancellationRequested检查下是否手动取消了,然后任务成功就返回,任务失败就延迟指定时间启动任务,递减重试次数。知道重试次数耗尽抛出异常。


下面是一个调用实例:


string stockHistory = await AsyncEx.Retry(() => DownloadStockHistory(symbol), 5, TimeSpan.FromSeconds(2));

2、错误处理


重试是一种错误处理机制。重试一直不行呢,就需要切换任务函数执行。使用Otherwise组合器接收两个任务,如果第一个任务失败就执行第二个任务。通过Status判断任务成功与否。

public static Task<T> Otherwise<T>(this Task<T> task, Func<Task<T>> orTask) =>  task.ContinueWith(async innerTask => { if (innerTask.Status == TaskStatus.Faulted) return await orTask(); return await Task.FromResult<T>(innerTask.Result); }).Unwrap();

调用实例如下:

Image image = await Retry(async () => await DownloadImageAsync("Bugghina001.jpg") .Otherwise(async () => await DownloadImageAsync("Bugghina002.jpg")), 5, TimeSpan.FromSeconds(2));

上面静态方法都是在AsyncEx静态帮助类中,以获得更加牢靠的任务执行。


3、再说一句


平常我做桌面开发和少量的web后台,用到并行的地方很少,我们体量没有那么大,一直处理单线程模式中,脑子也是单核的。并行理解确实晦涩难懂,还是应该慢慢熟练,走出安逸圈的过程必定痛苦的,现在有些许的明朗了。


前年说了很多函数组合器,下一节就重点看看这个,能给任务带来多丝滑的体验


    to be contiued!
下集:函数组合器




写给普通:

祝福所有低调善良的技术人

简单快乐



以上是关于.NET并发编程-异步编程模型下的主要内容,如果未能解决你的问题,请参考以下文章

python 闯关之路四(下)(并发编程与数据库编程) 并发编程重点

思考!低功耗设计的最佳编程模型:异步编程

python并发编程之IO模型

python之并发编程—IO模型

Python并发编程基础 №⑧ 并发完结篇:IO模型

python并发编程&IO模型