はじめに
最近、「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
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
内部実装では、オブジェクトが上書き (再割り当て) されると、不変コレクションは変更されたコレクションを返し、元のコレクション参照は変更されません。つまり、別の変数が同じオブジェクトを参照する場合、それは変更されません。 )は変わりません。
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
時間計算量
時々、このようなデータ構造が必要になります: インデックス付けをサポートし、頻繁に変更されず、複数のスレッドから安全にアクセスできます。
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
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
データを共有する必要がある状況、つまり、複数のスレッドが要素を追加するだけであり、要素を削除するだけのスレッドがある場合には、プロデューサー/コンシューマー コレクション (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) をご覧ください。