ホームページ  >  記事  >  バックエンド開発  >  C# 同時プログラミング · 古典的な例の読書メモ

C# 同時プログラミング · 古典的な例の読書メモ

黄舟
黄舟オリジナル
2017-02-06 16:43:461411ブラウズ

はじめに

最近、「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();

いずれか 1 つのタスクが完了するまで待ちます

複数のタスクを実行し、1 つのタスクの完了に応答するだけで済みますそのうちの。これは主に、操作に対して複数の独立した試行を実行するために使用されます。試行の 1 つが完了すると、タスクは完了します。

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

実際、2 つのスタックは内部で 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

は、きめ細かいロック技術とロックフリー技術を組み合わせて使用​​しており、最も実用的なコレクション タイプの 1 つです。

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 中国語 Web サイト (www.php.cn) をご覧ください。


声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。