代码改变世界

C# 并发编程 · 经典实例

2017-12-07 14:35  Dorisoy  阅读(1213)  评论(0编辑  收藏  举报

http://www.cnblogs.com/savorboard/p/csharp-concurrency-cookbook.html

异步基础

任务暂停,休眠

异步方式暂停或者休眠任务,可以使用 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

时间复杂度

操作ListImmutableList
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
}