首頁  >  文章  >  後端開發  >  C#並發程式設計·經典實例閱讀筆記

C#並發程式設計·經典實例閱讀筆記

黄舟
黄舟原創
2017-02-06 16:43:461448瀏覽

前言

最近在看《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 和 Process

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 和 Queue 非常相似。效能上,不可變堆疊(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

時間複雜度

C#並發程式設計·經典實例閱讀筆記

有些時候需要這樣一個資料結構:支持索引,不經常修改。

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 可以索引,但注意效能問題,不能用它來簡單的替代 List。它的內部實作是用的二元樹組織的數據,這麼做是為了讓不同的實例之間共享記憶體。

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 是最適合的。如果不會頻繁修改,那麼更適合使用 ImmutableDictionary

它最適合用於在需要共享資料的場合,即多個執行緒共享一個集合,如果一些執行緒只添加元素一些執行緒只移除元素,那麼最好使用生產者/消費者集合(BlockingCollection )。

初始化共享資源

程式多個地方使用一個值,第一次造訪時對它進行初始化。

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
}

以上就是C#並發程式設計·經典實例讀書筆記的內容,更多相關內容請關注PHP中文網(www.php.cn)!


陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn