デリゲートは、.NETリファレンスでスレッド化を容易にするオブジェクトの一部です。メソッドを非同期的に呼び出すために使用できます。フレームワーク 4.5 (またはそれ以前) には、スレッドの使用を容易にする、またはエラーを起こしにくくする他のオブジェクトはありますか?
並行性とマルチスレッド化を容易にする他の抽象化は何ですか?
注: この質問はこれを更新します。
デリゲートは、.NETリファレンスでスレッド化を容易にするオブジェクトの一部です。メソッドを非同期的に呼び出すために使用できます。フレームワーク 4.5 (またはそれ以前) には、スレッドの使用を容易にする、またはエラーを起こしにくくする他のオブジェクトはありますか?
並行性とマルチスレッド化を容易にする他の抽象化は何ですか?
注: この質問はこれを更新します。
私はマルチスレッドに関連する多くの質問に答える傾向があり、同じ基本的な質問がさまざまな方法で尋ねられることがよくあります。ここでは、私が何年にもわたって目にしてきた最も一般的な問題を紹介し、新しいテクノロジによってこれらの問題の解決がいかに容易になったかを説明します。
ループ変数を閉じる
これはスレッド化に固有の問題ではありませんが、スレッド化を使用すると問題が大きくなります。foreach
C# 5.0では、反復ごとに新しい変数を作成することで、ループに関するこの問題が修正されています。ラムダ式クロージャー用の特別な変数を作成する必要がなくなります。残念ながら、for
ループは特別なキャプチャ変数で処理する必要があります。
非同期タスクの完了を待機しています
.NET 4.0 では、CountdownEvent
多くのタスクの完了を待機するために必要な多くのロジックをカプセル化するクラスが導入されました。ほとんどのジュニア開発者はThread.Join
、通話または単一のWaitHandle.WaitAll
通話を使用しました。どちらもスケーラビリティの問題があります。古いパターンでは、シングルを使用しManualResetEvent
、カウンターがゼロに達したときに信号を送っていました。カウンターは、Interlocked
クラスを使用して更新されました。CountdownEvent
このパターンをはるかに簡単にします。すべてのワーカーがキューに入れられる前に 1 つのワーカーが終了した場合に発生する微妙な競合状態を回避するために、メインもワーカーとして扱うことを忘れないでください。
.NET 4.0 では、.NETTask
を介して子タスクを連鎖させることができるクラスも導入されましたTaskCreationOptions.AttachedToParent
。Task.Wait
親を呼び出すと、すべての子タスクも完了するまで待機します。
生産者と消費者
.NET 4.0 ではBlockingCollection
、コレクションが空のときにブロックできることを除いて、通常のキューのように機能するクラスが導入されました。を呼び出してオブジェクトをキューに入れたり、 を呼び出しAdd
てオブジェクトをデキューしたりできますTake
。Take
アイテムが利用可能になるまでブロックします。これにより、生産者と消費者のロジックが大幅に簡素化されます。以前は、開発者が独自のブロッキング キュー クラスを記述しようとしていたケースがありました。しかし、自分が何をしているのかわからなければ、本当に失敗する可能性があります...まずいです。実際、Microsoft は長い間、MSDN のドキュメントにブロッキング キューの例を掲載していましたが、それ自体がひどく壊れていました。幸いなことに、それはその後削除されました。
ワーカー スレッドの進行状況で UI を更新する
の導入によりBackgroundWorker
、初心者の開発者にとって、WinForm アプリケーションからのバックグラウンド タスクのスピンオフがはるかに簡単になりました。主な利点は、イベント ハンドラーReportProgress
内から呼び出すことができ、イベント ハンドラーが UI スレッドに自動的にマーシャリングされることです。もちろん、SO に関する私の回答を追跡している人なら誰でも、単純な進行状況情報で UI を更新するためのソリューションとして、マーシャリング操作 (経由など) について私がどのように感じているかを知っています。それは一般的にひどいアプローチなので、私はいつもそれを破っています。開発者は引き続き (バックグラウンドでのマーシャリング操作を介して) プッシュ モデルに強制されますが、少なくともこれはすべて舞台裏で行われます。DoWork
ProgressChanged
Invoke
BackgroundWorker
Invoke の優雅さ
UI 要素には UI スレッドからのみアクセスできることは誰もが知っています。ISynchronizeInvoke
これは一般に、開発者が、DispatcherObject
、またはを介してマーシャリング操作を使用して、SynchronizationContext
制御を UI スレッドに戻す必要があることを意味していました。しかし、それに直面しましょう。これらのマーシャリング操作は醜く見えます。Task.ContinueWith
これをもう少しエレガントにしましたが、真の栄光はawait
C# 5 の新しい非同期プログラミング モデルの一部として発揮されます。を使用して、タスクの実行中にフロー制御が一時的に中断され、適切な同期コンテキストのまさにその時点で返されるような方法で が完了するawait
のを待つことができます。これらすべての呼び出しの代わりとしてTask
使用することほど、エレガントで満足のいくものはありません。await
Invoke
並列プログラミング
どうすれば並列処理できるのかという質問をよく見かけます。古い方法では、いくつかのスレッドを作成するか、ThreadPool
. .NET 4.0 では、TPL と PLINQ が使用されました。このParallel
クラスは、ループの反復を並行して行うための優れた方法です。そして、PLINQAsParallel
は単純な古い LINQ の同じコインの別の側面です。これらの新しい TPL 機能により、このカテゴリのマルチスレッド プログラミングが大幅に簡素化されます。
.NET 4.5 では、TPL データ フロー ライブラリが導入されています。複雑な並列プログラミングの問題をエレガントにすることを目的としています。クラスをブロックに抽象化します。それらは、ターゲット ブロックまたはソース ブロックになります。データは、あるブロックから別のブロックに流れることができます。BufferBlock<T>
、BroadcastBlock<T>
、などを含む多くの異なるブロックがありActionBlock<T>
、すべて異なることを行います。async
そしてもちろん、ライブラリ全体が newおよびawait
キーワードで使用できるように最適化されます。これは、ゆっくりと普及すると思われるエキサイティングな新しいクラスのセットです。
グレースフル ターミネーション
スレッドを停止するにはどうすればよいですか? この質問をよく見かけます。最も簡単な方法は を呼び出すことですThread.Abort
が、これを行うことの危険性は誰もが知っています...願っています。これを安全に行うには、さまざまな方法があります。CancellationToken
.NET 4.0 では、 と による取り消しと呼ばれる、より統一された概念が導入されましたCancellationTokenSource
。バックグラウンド タスクは、安全なポイントでポーリングIsCancellationRequested
または単に呼び出して、実行中の作業ThrowIfCancellationRequested
を適切に中断することができます。他のスレッドはCancel
、キャンセルを要求するために呼び出すことができます。
さてここで見てみましょう:
ThreadPool
まだ信頼できます。BackgoundWorker
(.NET 2.0+)-GUIアプリケーションのバックグラウンドでタスクを実行するための便利な機能を提供する、もう1つの昔ながらの構造。Timer
■-バックグラウンドスレッドを使用して、指定された間隔でコードを実行する場合に便利です。Task
.NET 4.0+)-基になるスレッドプールで実行され、例外マーシャリングやスケジューリングなどの多くの便利な機能を提供するスレッド抽象化。いわゆる「タスク並列処理」パターンに役立ちます。Parallel.For
、Parallel.ForEach
(。NET 4.0+)-一連のデータに対して同じ操作を並行して実行するのに適しています。いわゆる「データ並列処理」パターンに役立ちます。Parallel.Invoke
(.NET 4.0+)-sをさらに抽象化しTask
ます。いくつかのコード(メソッド、ラムダ)を並行して起動するだけです。間違いなく、新しいTpl DataFlowライブラリ (.net 4.5 に含まれる) を理解することで、並行開発という点で最大のブーストが得られます。
高度な同時実行アプリに真剣に取り組んでいる場合は、DataFlow に慣れるために 1 日か 2 日を費やしてください。それは真剣に良いです。
Task
とTask<T>
、しかしそれらは .NET 4 以来ここにありました。はasync
必ずしもスレッドで動作するとは限りません。非常に良い説明については、Øredev からの Jon のビデオを参照してください。