最近在看《C# 并发编程 · 经典实例》这本书,这不是一本理论书,反而这是一本主要讲述怎么样更好的使用好目前 C#.NET 为我们提供的这些 API 的一本书,书中绝大部分是一些实例,在日常开发中还是经常会使用到。
书中一些观点还是比较赞同,比如作者说目前绝大多数的图书对关于并发多线程等这些内容放到最后,而缺少一本介绍并发编程的入门指引和参考。另外一个观点是绝大多数国内的技术人员认为技术越底层就牛逼,而做上层应用的就是“码农”,作者反对了这一观点,其实能利用好现有的库也是一种能力,虽然说理解基础知识对日常生活仍然有帮助,但最好从更高级的抽象概念来学习。
异步方式暂停或者休眠任务,可以使用 Task.Delay()
;
1 2 3 4 |
<span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> Task<T> DelayResult<T>(T result, TimeSpan delay) { <span class="hljs-keyword">await</span> Task.Delay(delay); <span class="hljs-keyword">return</span> result; } |
一个简单的指数退避策略,重试的时间会逐次增加,在访问 Web 服务时,一般采用此种策略。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> Task<<span class="hljs-keyword">string</span>> <span class="hljs-title">DownloadString</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> uri</span>) </span>{ <span class="hljs-keyword">using</span> (<span class="hljs-keyword">var</span> client = <span class="hljs-keyword">new</span> HttpClient()) { <span class="hljs-keyword">var</span> nextDealy = TimeSpan.FromSeconds(<span class="hljs-number">1</span>); <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i != <span class="hljs-number">3</span>; ++i) { <span class="hljs-keyword">try</span> { <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> client.GetStringAsync(uri); } <span class="hljs-keyword">catch</span> { } <span class="hljs-keyword">await</span> Task.Delay(nextDealy); nextDealy = nextDealy + nextDealy; } <span class="hljs-comment">//最后重试一次,抛出出错信息 </span> <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> client.GetStringAsync(uri); } } |
异步操作中,经常需要展示操作进度,可以使用 IProcess<T>
和 Process<T>
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
<span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">MyMethodAsync</span>(<span class="hljs-params">IProgress<<span class="hljs-keyword">double</span>> progress</span>) </span>{ <span class="hljs-keyword">double</span> precentComplete = <span class="hljs-number">0</span>; <span class="hljs-keyword">bool</span> done = <span class="hljs-keyword">false</span>; <span class="hljs-keyword">while</span> (!done) { <span class="hljs-keyword">await</span> Task.Delay(<span class="hljs-number">100</span>); <span class="hljs-keyword">if</span> (progress != <span class="hljs-keyword">null</span>) { progress.Report(precentComplete); } precentComplete++; <span class="hljs-keyword">if</span> (precentComplete == <span class="hljs-number">100</span>) { done = <span class="hljs-keyword">true</span>; } } } <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Main</span>(<span class="hljs-params"><span class="hljs-keyword">string</span>[] args</span>) </span>{ Console.WriteLine(<span class="hljs-string">"starting..."</span>); <span class="hljs-keyword">var</span> progress = <span class="hljs-keyword">new</span> Progress<<span class="hljs-keyword">double</span>>(); progress.ProgressChanged += (sender, e) => { Console.WriteLine(e); }; MyMethodAsync(progress).Wait(); Console.WriteLine(<span class="hljs-string">"finished"</span>); } |
同时执行几个任务,等待他们全部完成
1 2 3 4 5 |
Task task1 = Task.Delay(TimeSpan.FromSeconds(<span class="hljs-number">1</span>)); Task task2 = Task.Delay(TimeSpan.FromSeconds(<span class="hljs-number">2</span>)); Task task3 = Task.Delay(TimeSpan.FromSeconds(<span class="hljs-number">1</span>)); Task.WhenAll(task1, task2, task3).<span class="hljs-keyword">Wait</span>(); |
执行若干任务,只需要对其中一个的完成进行响应。主要用于对一个操作进行多种独立的尝试,只要其中一个尝试完成,任务就算完成。
1 2 3 4 5 6 7 8 9 10 11 12 |
<span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> Task<<span class="hljs-keyword">int</span>> <span class="hljs-title">FirstResponseUrlAsync</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> urlA, <span class="hljs-keyword">string</span> urlB</span>) </span>{ <span class="hljs-keyword">var</span> httpClient = <span class="hljs-keyword">new</span> HttpClient(); Task<<span class="hljs-keyword">byte</span>[]> downloadTaskA = httpClient.GetByteArrayAsync(urlA); Task<<span class="hljs-keyword">byte</span>[]> downloadTaskB = httpClient.GetByteArrayAsync(urlB); Task<<span class="hljs-keyword">byte</span>[]> completedTask = <span class="hljs-keyword">await</span> Task.WhenAny(downloadTaskA, downloadTaskB); <span class="hljs-keyword">byte</span>[] data = <span class="hljs-keyword">await</span> completedTask; <span class="hljs-keyword">return</span> data.Length; } |
需要一个不会经常修改,可以被多个线程安全访问的栈和队列。他们的API和 Stack<T>
和 Queue<T>
非常相似。性能上,不可变栈(LIFO)和队列(FIFO)与标准的栈和队列具有相同的时间复杂度。但是在需要频繁修改的简单情况下,标准栈和队列速度更快。
在内部实现上,当对一个对象进行覆盖(重新赋值)的时候,不可变集合采用的是返回一个修改过的集合,原始集合引用是不变化的,也就是说如果另外一个变量引用了相同的对象,那么它(另外的变量)是不会变化的。
ImmutableStack
1 2 3 4 5 6 7 8 9 10 11 |
<span class="hljs-keyword">var</span> stack = ImmutableStack<<span class="hljs-keyword">int</span>>.Empty; stack = stack.Push(<span class="hljs-number">11</span>); <span class="hljs-keyword">var</span> biggerstack = stack.Push(<span class="hljs-number">12</span>); <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> item <span class="hljs-keyword">in</span> biggerstack) { Console.WriteLine(item); } <span class="hljs-comment">// output: 12 11</span> <span class="hljs-keyword">int</span> lastItem; stack = stack.Pop(<span class="hljs-keyword">out</span> lastItem); Console.WriteLine(lastItem); <span class="hljs-comment">//output: 11</span> |
实际上,两个栈内部共享了存储 11 的内存,这种实现方式效率很高,而且每个实例都是线程安全的。
ImmutableQueue
1 2 3 4 5 6 7 8 9 10 11 12 |
<span class="hljs-keyword">var</span> queue = ImmutableQueue<<span class="hljs-keyword">int</span>>.Empty; queue = queue.Enqueue(<span class="hljs-number">11</span>); queue = queue.Enqueue(<span class="hljs-number">12</span>); <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> item <span class="hljs-keyword">in</span> queue) { Console.WriteLine(item); } <span class="hljs-comment">// output: 11 12</span> <span class="hljs-keyword">int</span> nextItem; queue = queue.Dequeue(<span class="hljs-keyword">out</span> nextItem); Console.WriteLine(nextItem); <span class="hljs-comment">//output: 11</span> |
ImmutableList
时间复杂度
操作 | List | ImmutableList |
---|---|---|
Add | O(1) | O(log N) |
Insert | O(log N) | O(log N) |
RemoveAt | O(log N) | O(log N) |
Item[index] | O(1) | O(log N) |
有些时候需要这样一个数据结构:支持索引,不经常修改,可以被多线程安全的访问。
1 2 3 4 5 6 7 8 9 |
<span class="hljs-keyword">var</span> list = ImmutableList<<span class="hljs-keyword">int</span>>.Empty; list = list.Insert(<span class="hljs-number">0</span>, <span class="hljs-number">11</span>); list = list.Insert(<span class="hljs-number">0</span>, <span class="hljs-number">12</span>); <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> item <span class="hljs-keyword">in</span> list) { Console.WriteLine(item); } <span class="hljs-comment">// 12 11</span> |
ImmutableList<T>
可以索引,但是注意性能问题,不能用它来简单的替代 List<T>
。它的内部实现是用的二叉树组织的数据,这么做是为了让不同的实例之间共享内存。
ImmutableHashSet
有些时候需要这样一个数据结构:不需要存放重复内容,不经常修改,可以被多个线程安全访问。时间复杂度 O(log N)。
1 2 3 4 5 6 7 8 |
<span class="hljs-keyword">var</span> <span class="hljs-keyword">set</span> = ImmutableHashSet<<span class="hljs-keyword">int</span>>.Empty; <span class="hljs-keyword">set</span> = <span class="hljs-keyword">set</span>.Add(<span class="hljs-number">11</span>); <span class="hljs-keyword">set</span> = <span class="hljs-keyword">set</span>.Add(<span class="hljs-number">12</span>); <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> item <span class="hljs-keyword">in</span> <span class="hljs-keyword">set</span>) { Console.WriteLine(item); } <span class="hljs-comment">// 11 12 顺序不定</span> |
一个线程安全的键值对集合,多个线程读写仍然能保持同步。
ConcurrentDictionary
混合使用了细粒度的锁定和无锁技术,它是最实用的集合类型之一。
1 2 |
<span class="hljs-keyword">var</span> dictionary = <span class="hljs-keyword">new</span> ConcurrentDictionary<<span class="hljs-keyword">int</span>, <span class="hljs-keyword">string</span>>(); dictionary.AddOrUpdate(<span class="hljs-number">0</span>, key => <span class="hljs-string">"Zero"</span>, (key, oldValue) => <span class="hljs-string">"Zero"</span>); |
如果多个线程读写一个共享集合,实用 ConcurrentDictionary<TKey,TValue>
是最合适的。如果不会频繁修改,那么更适合使用 ImmutableDictionary<TKey,TValue>
。
它最适合用于在需要共享数据的场合,即多个线程共享一个集合,如果一些线程只添加元素一些线程只移除元素,那最好使用 生产者/消费者集合(BlockingCollection<T>
)。
程序多个地方使用一个值,第一次访问时对它进行初始化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<span class="hljs-keyword">static</span> <span class="hljs-keyword">int</span> _simpleVluae; <span class="hljs-keyword">static</span> <span class="hljs-keyword">readonly</span> Lazy<Task<<span class="hljs-keyword">int</span>>> shardAsyncInteger = <span class="hljs-keyword">new</span> Lazy<Task<<span class="hljs-keyword">int</span>>>(<span class="hljs-keyword">async</span> () => { <span class="hljs-keyword">await</span> Task.Delay(<span class="hljs-number">2000</span>).ConfigureAwait(<span class="hljs-keyword">false</span>); <span class="hljs-keyword">return</span> _simpleVluae++; }); <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Main</span>(<span class="hljs-params"><span class="hljs-keyword">string</span>[] args</span>) </span>{ <span class="hljs-keyword">int</span> shareValue = shardAsyncInteger.Value.Result; Console.WriteLine(shareValue); <span class="hljs-comment">// 0</span> shareValue = shardAsyncInteger.Value.Result; Console.WriteLine(shareValue); <span class="hljs-comment">// 0</span> shareValue = shardAsyncInteger.Value.Result; Console.WriteLine(shareValue); <span class="hljs-comment">// 0</span> } |
本文地址:http://www.cnblogs.com/savorboard/p/csharp-concurrency-cookbook.html
作者博客:Savorboard
欢迎转载,请在明显位置给出出处及链接