@wddpct
2017-04-03T21:28:19.000000Z
字数 6788
阅读 2521
c#
最近趁着项目的一段平稳期研读了不少书籍,其中《C#并发编程经典实例》给我的印象还是比较深刻的。当然,这可能是由于近段日子看的书大多嘴炮大于实际,如《Head First设计模式》《Cracking the coding interview》等,所以陡然见到一本打着“实例”旗号的书籍,还是挺让我觉得耳目一新。本着分享和加深理解的目的,我特地整理了一些笔记(主要是Web开发中容易涉及的内容,所以部分章节如数据流,RX等我看了看就直接跳过了),以供审阅学习。语言和技术的魅力,真是不可捉摸
一直以来都有一种观点是实现底层架构,编写驱动和引擎,或者是框架和工具开发的才是高级开发人员,做上层应用的人仅仅是“码农”,其实能够利用好平台提供的相关类库,而不是全部采用底层技术自己实现,开发出高质量,稳定的应用程序,对技术能力的考验并不低于开发底层库,如TPL,async,await等。
static async Task<string> DownloadStringWithRetries(string uri)
{
using (var client = new HttpClient())
{
// 第 1 次重试前等 1 秒,第 2 次等 2 秒,第 3 次等 4 秒。
var nextDelay = TimeSpan.FromSeconds(1);
for (int i = 0; i != 3; ++i)
{
try
{
return await client.GetStringAsync(uri);
}
catch
{ }
await Task.Delay(nextDelay);
nextDelay = nextDelay + nextDelay;
}
// 最后重试一次,以便让调用者知道出错信息。
return await client.GetStringAsync(uri);
}
}
static void ProcessArray(double[] array)
{
Parallel.Invoke(
() => ProcessPartialArray(array, 0, array.Length / 2),
() => ProcessPartialArray(array, array.Length / 2,array.Length));
}
static void ProcessPartialArray(double[] array, int begin, int end)
{
// 计算密集型的处理过程 ...
}
这里附上一个ABP中实现的可操作AsyncHelper类,就是基于AsyncContext实现
/// <summary>
/// Provides some helper methods to work with async methods.
/// </summary>
public static class AsyncHelper
{
/// <summary>
/// Checks if given method is an async method.
/// </summary>
/// <param name="method">A method to check</param>
public static bool IsAsyncMethod(MethodInfo method)
{
return (
method.ReturnType == typeof(Task) ||
(method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>))
);
}
/// <summary>
/// Runs a async method synchronously.
/// </summary>
/// <param name="func">A function that returns a result</param>
/// <typeparam name="TResult">Result type</typeparam>
/// <returns>Result of the async operation</returns>
public static TResult RunSync<TResult>(Func<Task<TResult>> func)
{
return AsyncContext.Run(func);
}
/// <summary>
/// Runs a async method synchronously.
/// </summary>
/// <param name="action">An async action</param>
public static void RunSync(Func<Task> action)
{
AsyncContext.Run(action);
}
}
线程安全集合是可同时被多个线程修改的可变集合。线程安全集合混合使用了细粒度锁定和无锁技术,以确保线程被阻塞的时间最短(通常情况下是根本不阻塞)。对很多线程安全集合进行枚举操作时,内部创建了该集合的一个快照(snapshot),并对这个快照进行枚举操作。线程安全集合的主要优点是多个线程可以安全地对其进行访问,而代码只会被阻塞很短的时间,或根本不阻塞
ConcurrentDictionary是数据结构中的精品,它是线程安全的,混合使用了细粒度锁定和无锁技术,以确保绝大多数情况下能进行快速访问.
ConcurrentDictionary 内置了AddOrUpdate, TryRemove, TryGetValue等方法。如果多个线程读写一个共享集合,使用ConcurrentDictionary是最合适的,如果不会频繁修改,那就更适合使用ImmutableDictionary。而如果是一些线程只添加元素,一些线程只移除元素,最好使用生产者/消费者集合
异步编程是函数式的(functional),.NET 引入的async让开发者进行异步编程的时候也能用过程式编程的思维来进行思考,但是在内部实现上,异步编程仍然是函数式的
伟人说过,世界既是过程式的,也是函数式的,但是终究是函数式的
可以用await等待的是一个类(如Task对象),而不是一个方法。可以用await等待某个方法返回的Task,无论它是不是async方法。
类的构造函数里是不能进行异步操作的,一般可以使用如下方法。相应的,我们可以通过var instance=new Program.CreateAsync();
class Program
{
private Program()
{
}
private async Task<Program> InitializeAsync()
{
await Task.Delay(TimeSpan.FromSeconds(1));
return this;
}
public static Task<Program> CreateAsync()
{
var result = new Program();
return result.InitializeAsync();
}
}
同步的类型主要有两种:通信和数据保护
如果下面三个条件都满足,就需要用同步来保护共享的数据
观察以下代码,确定其同步和运行状态
class SharedData
{
public int Value { get; set; }
}
async Task ModifyValueAsync(SharedData data)
{
await Task.Delay(TimeSpan.FromSeconds(1));
data.Value = data.Value + 1;
}
// 警告:可能需要同步,见下面的讨论。
async Task<int> ModifyValueConcurrentlyAsync()
{
var data = new SharedData();
// 启动三个并发的修改过程。
var task1 = ModifyValueAsync(data);
var task2 = ModifyValueAsync(data);
var task3 = ModifyValueAsync(data);
await Task.WhenAll(task1, task2, task3);
return data.Value;
}
本例中,启动了三个并发运行的修改过程。需要同步吗?答案是“看情况”。如果能确定 这个方法是在 GUI 或 ASP.NET 上下文中调用的(或同一时间内只允许一段代码运行的任 何其他上下文),那就不需要同步,因为这三个修改数据过程的运行时间是互不相同的。 例如,如果它在 GUI 上下文中运行,就只有一个 UI 线程可以运行这些数据修改过程,因 此一段时间内只能运行一个过程。因此,如果能够确定是“同一时间只运行一段代码”的 上下文,那就不需要同步。但是如果从线程池线程(如 Task.Run)调用这个方法,就需要同步了。在那种情况下,这三个数据修改过程会在独立的线程池线程中运行,并且同时修改 data.Value,因此必须同步地访问 data.Value。
不可变类型本身就是线程安全的,修改一个不可变集合是不可能的,即便使用多个Task.Run向集合中添加数据,也并不需要同步操作
线程安全集合(例如 ConcurrentDictionary)就完全不同了。与不可变集合不同,线程安 全集合是可以修改的。线程安全集合本身就包含了所有的同步功能
关于锁的使用,有四条重要的准则
如果需要异步锁,请尝试 SemaphoreSlim
不要在 ASP. NET 中使用 Task.Run,这是因为在 ASP.NET 中,处理请求的代码本来就是在线程池线程中运行的,强行把它放到另一个线程池线程通常会适得其反
(7) 实用技巧
static int _simpleValue;
static readonly Lazy<Task<int>> MySharedAsyncInteger =
new Lazy<Task<int>>(() =>
Task.Run(async () =>
{
await Task.Delay(TimeSpan.FromSeconds(2));
return _simpleValue++;
}));
async Task GetSharedIntegerAsync()
{
int sharedValue = await MySharedAsyncInteger.Value;
}