close
文章出處

前言

最近在看《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
歡迎轉載,請在明顯位置給出出處及鏈接


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜

    AutoPoster 發表在 痞客邦 留言(0) 人氣()