前言
最近在看《C# 并发编程 · 经典实例》这本书,这不是一本理论书,反而这是一本主要讲述怎么样更好的使用好目前 C#.NET 为我们提供的这些 API 的一本书,书中绝大部分是一些实例,在日常开发中还是经常会使用到。
书中一些观点还是比较赞同,比如作者说目前绝大多数的图书对关于并发多线程等这些内容放到最后,而缺少一本介绍并发编程的入门指引和参考。另外一个观点是绝大多数国内的技术人员认为技术越底层就牛逼,而做上层应用的就是“码农”,作者反对了这一观点,其实能利用好现有的库也是一种能力,虽然说理解基础知识对日常生活仍然有帮助,但最好从更高级的抽象概念来学习。
异步基础
任务暂停,休眠
异步方式暂停或者休眠任务,可以使用 Task.Delay()
;
static async Task<T> DelayResult<T>(T result, TimeSpan delay) {
await Task.Delay(delay);
return result;
}
异步重试机制
一个简单的指数退避策略,重试的时间会逐次增加,在访问 Web 服务时,一般采用此种策略。
static async Task<string> DownloadString(string uri) {
using (var client = new HttpClient()) {
var nextDealy = TimeSpan.FromSeconds(1);
for (int i = 0; i != 3; ++i) {
try {
return await client.GetStringAsync(uri);
}
catch {
}
await Task.Delay(nextDealy);
nextDealy = nextDealy + nextDealy;
}
//最后重试一次,抛出出错信息
return await client.GetStringAsync(uri);
}
}
报告进度
异步操作中,经常需要展示操作进度,可以使用 IProcess<T>
和 Process<T>
。
static async Task MyMethodAsync(IProgress<double> progress) {
double precentComplete = 0;
bool done = false;
while (!done) {
await Task.Delay(100);
if (progress != null) {
progress.Report(precentComplete);
}
precentComplete++;
if (precentComplete == 100) {
done = true;
}
}
}
public static void Main(string[] args) {
Console.WriteLine("starting...");
var progress = new Progress<double>();
progress.ProgressChanged += (sender, e) => {
Console.WriteLine(e);
};
MyMethodAsync(progress).Wait();
Console.WriteLine("finished");
}
等待一组任务
同时执行几个任务,等待他们全部完成
Task task1 = Task.Delay(TimeSpan.FromSeconds(1));
Task task2 = Task.Delay(TimeSpan.FromSeconds(2));
Task task3 = Task.Delay(TimeSpan.FromSeconds(1));
Task.WhenAll(task1, task2, task3).Wait();
等待任意一个任务完成
执行若干任务,只需要对其中一个的完成进行响应。主要用于对一个操作进行多种独立的尝试,只要其中一个尝试完成,任务就算完成。
static async Task<int> FirstResponseUrlAsync(string urlA, string urlB) {
var httpClient = new HttpClient();
Task<byte[]> downloadTaskA = httpClient.GetByteArrayAsync(urlA);
Task<byte[]> downloadTaskB = httpClient.GetByteArrayAsync(urlB);
Task<byte[]> completedTask = await Task.WhenAny(downloadTaskA, downloadTaskB);
byte[] data = await completedTask;
return data.Length;
}
集合
不可变栈和队列
需要一个不会经常修改,可以被多个线程安全访问的栈和队列。他们的API和 Stack<T>
和 Queue<T>
非常相似。性能上,不可变栈(LIFO)和队列(FIFO)与标准的栈和队列具有相同的时间复杂度。但是在需要频繁修改的简单情况下,标准栈和队列速度更快。
在内部实现上,当对一个对象进行覆盖(重新赋值)的时候,不可变集合采用的是返回一个修改过的集合,原始集合引用是不变化的,也就是说如果另外一个变量引用了相同的对象,那么它(另外的变量)是不会变化的。
ImmutableStack
var stack = ImmutableStack<int>.Empty;
stack = stack.Push(11);
var biggerstack = stack.Push(12);
foreach (var item in biggerstack) {
Console.WriteLine(item);
} // output: 12 11
int lastItem;
stack = stack.Pop(out lastItem);
Console.WriteLine(lastItem); //output: 11
实际上,两个栈内部共享了存储 11 的内存,这种实现方式效率很高,而且每个实例都是线程安全的。
ImmutableQueue
var queue = ImmutableQueue<int>.Empty;
queue = queue.Enqueue(11);
queue = queue.Enqueue(12);
foreach (var item in queue) {
Console.WriteLine(item);
} // output: 11 12
int nextItem;
queue = queue.Dequeue(out nextItem);
Console.WriteLine(nextItem); //output: 11
不可变列表和集合
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) |
有些时候需要这样一个数据结构:支持索引,不经常修改,可以被多线程安全的访问。
var list = ImmutableList<int>.Empty;
list = list.Insert(0, 11);
list = list.Insert(0, 12);
foreach (var item in list) {
Console.WriteLine(item);
} // 12 11
ImmutableList<T>
可以索引,但是注意性能问题,不能用它来简单的替代 List<T>
。它的内部实现是用的二叉树组织的数据,这么做是为了让不同的实例之间共享内存。
ImmutableHashSet
有些时候需要这样一个数据结构:不需要存放重复内容,不经常修改,可以被多个线程安全访问。时间复杂度 O(log N)。
var set = ImmutableHashSet<int>.Empty;
set = set.Add(11);
set = set.Add(12);
foreach (var item in set) {
Console.WriteLine(item);
} // 11 12 顺序不定
线程安全字典
一个线程安全的键值对集合,多个线程读写仍然能保持同步。
ConcurrentDictionary
混合使用了细粒度的锁定和无锁技术,它是最实用的集合类型之一。
var dictionary = new ConcurrentDictionary<int, string>();
dictionary.AddOrUpdate(0, key => "Zero", (key, oldValue) => "Zero");
如果多个线程读写一个共享集合,实用 ConcurrentDictionary<TKey,TValue>
是最合适的。如果不会频繁修改,那么更适合使用 ImmutableDictionary<TKey,TValue>
。
它最适合用于在需要共享数据的场合,即多个线程共享一个集合,如果一些线程只添加元素一些线程只移除元素,那最好使用 生产者/消费者集合(BlockingCollection<T>
)。
初始化共享资源
程序多个地方使用一个值,第一次访问时对它进行初始化。
static int _simpleVluae;
static readonly Lazy<Task<int>> shardAsyncInteger =
new Lazy<Task<int>>(async () => {
await Task.Delay(2000).ConfigureAwait(false);
return _simpleVluae++;
});
public static void Main(string[] args) {
int shareValue = shardAsyncInteger.Value.Result;
Console.WriteLine(shareValue); // 0
shareValue = shardAsyncInteger.Value.Result;
Console.WriteLine(shareValue); // 0
shareValue = shardAsyncInteger.Value.Result;
Console.WriteLine(shareValue); // 0
}
本文地址:http://www.cnblogs.com/savorboard/p/csharp-concurrency-cookbook.html
作者博客:Savorboard
欢迎转载,请在明显位置给出出处及链接
《C# 并发编程 · 经典实例》读书笔记的更多相关文章
-
《C#并发编程经典实例》笔记
1.前言 2.开宗明义 3.开发原则和要点 (1)并发编程概述 (2)异步编程基础 (3)并行开发的基础 (4)测试技巧 (5)集合 (6)函数式OOP (7)同步 1.前言 最近趁着项目的一段平稳期 ...
-
《C#并发编程经典实例》学习笔记—2.7 避免上下文延续
避免上下文延续 在默认情况下,一个 async 方法在被 await 调用后恢复运行时,会在原来的上下文中运行. 为了避免在上下文中恢复运行,可让 await 调用 ConfigureAwait 方法 ...
-
《C#并发编程经典实例》学习笔记—3.1 数据的并行处理
问题 有一批数据,需要对每个元素进行相同的操作.该操作是计算密集型的,需要耗费一定的时间. 解决方案 常见的操作可以粗略分为 计算密集型操作 和 IO密集型操作.计算密集型操作主要是依赖于CPU计算, ...
-
《C#并发编程经典实例》学习笔记—2.3 报告任务
问题 异步操作时,需要展示该操作的进度 解决方案 IProgress<T> Interface和Progress<T> Class 插一段话:读<C#并发编程经典实例&g ...
-
Java并发编程的艺术读书笔记(2)-并发编程模型
title: Java并发编程的艺术读书笔记(2)-并发编程模型 date: 2017-05-05 23:37:20 tags: ['多线程','并发'] categories: 读书笔记 --- 1 ...
-
Java并发编程的艺术读书笔记(1)-并发编程的挑战
title: Java并发编程的艺术读书笔记(1)-并发编程的挑战 date: 2017-05-03 23:28:45 tags: ['多线程','并发'] categories: 读书笔记 --- ...
-
[书籍]用UWP复习《C#并发编程经典实例》
1. 简介 C#并发编程经典实例 是一本关于使用C#进行并发编程的入门参考书,使用"问题-解决方案-讨论"的模式讲解了以下这些概念: 面向异步编程的async和await 使用TP ...
-
《Go并发编程实战》读书笔记-语法概览
<Go并发编程实战>读书笔记-语法概览 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 本篇博客我们会快速浏览一下Go的语法,内容涉及基本构成要素(比如标识符,关键字,子 ...
-
《Go并发编程实战》读书笔记-初识Go语言
<Go并发编程实战>读书笔记-初识Go语言 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 在讲解怎样用Go语言之前,我们先介绍Go语言的特性,基础概念和标准命令. 一. ...
随机推荐
-
.NET Core 和 .NET Framework 之间的关系
引用一段描述:Understanding the relationship between .NET Core and the .NET Framework. .NET Core and the .N ...
-
swift-懒加载
override func viewDidLoad() { super.viewDidLoad() self.view.backgroundColor = UIColor.cyan self.navi ...
-
webApi中参数传递
webApi中参数传递 一:无参数的get方法: 前端: function GetNoParam() { //为了统一:我们都采用$.ajax({}) 方法; $.ajax({ url: '/a ...
-
【BZOJ】【1901】【Zju2112】 Dynamic Rankings
再填个坑. 动态维护区间第K大(带单点修改) 首先裸的区间第K大我们是用的[前缀和]思想,实现O(n)预处理,O(1)找树查询,那么如果是动态的呢?我们可以利用树状数组(BIT)的思想,进行O(log ...
-
NetworkInfo 手机中的网络类型
04-27 21:56:54.442: E/NetworkInfo(26457): NetworkInfo: type: mobile[EDGE], state: DISCONNECTED/IDLE, ...
-
HDU 5619 Jam&#39;s store
Jam's store Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)Total ...
-
【转】MUD教程--巫师入门教程4
我们再次复习一下clean_up()函数返回1的含义,如果clean_up()函数返回1,则MUDOS在这一次的调用时不会做其的任何举动,但到了下一次想调用的时间里,还将再次调用这个对象的clean_ ...
-
[SinGuLaRiTy] Nescafe 24杯模拟赛
[SinGularLaRiTy-1044] Copyright (c) SinGuLaRiTy 2017. All Rights Reserved. 小水塘(lagoon) 题目描述 忘川沧月的小水塘 ...
-
JAVA自动补全代码
打开eclipse(对myeclipse同样适用) 找到窗口(windows)菜单,打开最后一项首选项(Preferences)找到下属菜单java打开,打开里边的编辑器(Editor)菜单,点击内容 ...
-
js验证手机号码,邮箱,qq号
function validateMail(str:String) { var re_m = /^[a-z\d]+(\.[a-z\d]+)*@([\da-z](-[\da-z])?)+(\.{1,2} ...