流暢なアプローチは 2 つのタイプに分けることができます。変化するものと変化しないもの。
変更ケースは .NET ではあまり一般的ではありません (流暢なアプローチは、Linq が導入されるまでは一般的ではありませんでした。これは、流暢なアプローチの非常に頻繁な使用です。比較すると、Java は、C# の代わりにプロパティを使用して同じ構文を提供するプロパティ セッターでそれらを頻繁に使用します。フィールドの設定としてプロパティを設定します)。ただし、1 つの例はStringBuilder
.
StringBuilder sb = new StringBuilder("a").Append("b").Append("c");
基本的な形式は次のとおりです。
TypeOfContainingClass SomeMethod(/*... arguments ... */)
{
//Do something, generally mutating the current object
//though we could perhaps mix in some non-mutating methods
//with the mutating methods a class like this uses, for
//consistency.
return this;
}
これは、問題のオブジェクトを変更するため、本質的に非スレッドセーフなアプローチであり、異なるスレッドからの 2 つの呼び出しが干渉します。そのような呼び出しに直面しても、一貫性のない状態にならないという意味でスレッドセーフなクラスを作成することは確かに可能ですが、一般的に、このアプローチを採用する場合、それらの変更の結果を気にします。そしてそれらだけ。たとえば、上記の例では、 string を最終的に保持するStringbBuilder
ことを気にします。スレッドセーフは無意味です。これは、それが正常に保持されるか、受け入れられるかの保証を考慮しないためです。そのような仮想クラス自体はスレッドセーフですが、呼び出しコードはそうではありません。sb
"abc"
StringBuilder
"abc"
"acb"
(これは、スレッド セーフ コードでそのようなクラスを使用できないという意味ではありません。スレッド セーフ コードで任意のクラスを使用できますが、役に立ちません)。
現在、非変更形式は、それ自体がスレッドセーフです。これは、すべての使用がスレッドセーフであることを意味するわけではありませんが、可能であることを意味します。次の LINQ コードを検討してください。
var results = someSource
.Where(somePredicate)
.OrderBy(someOrderer)
.Select(someFactory);
これは、次の場合に限りスレッド セーフです。
- someSource の反復処理はスレッドセーフです。
- somePredicate の呼び出しはスレッドセーフです。
- someOrder の呼び出しはスレッドセーフです。
- someFactory の呼び出しはスレッドセーフです。
これは多くの基準のように見えるかもしれませんが、実際には、最後の基準はすべて同じ基準です:Func
インスタンスが機能する必要があります - インスタンスには副作用*がなく、入力に応じた結果を返します。 (スレッドセーフでありながら機能的であることに関するいくつかのルールを曲げることはできますが、今は複雑にしないでください)。とまぁ、そういうケースを想定して名前をつけたのだろうFunc
。Linq の最も一般的な種類のケースがこの説明に当てはまることに注意してください。例えば:
var results = someSource
.Where(item => item.IsActive)//functional. Thread-safe as long as accessing IsActive is.
.OrderBy(item => item.Priority)//functional. Thread-safe as long as accessing Priority is.
.Select(item => new {item.ID, item.Name});//functional. Thread-safe as long as accessing ID and Name is.
現在、プロパティの実装の 99% で、getter
複数のスレッドからの s の呼び出しは、別のスレッドの書き込みがない限り、スレッドセーフです。これは一般的なシナリオであるため、そのようなケースに安全に対応できるという点ではスレッドセーフですが、別のスレッドがそのような変更を行っている場合はスレッドセーフではありません。
同様に、次のようなソースsomeSource
を 4 つのカテゴリに分割できます。
- メモリ内のコレクション。
- データベースまたはその他のデータ ソースに対する呼び出し。
- どこかから取得した情報を 1 回通過するが、2 回目の反復でその情報を再度取得するために必要な情報がソースにない列挙型。
- 他の。
最初のケースの大部分は、他のリーダーだけに直面してもスレッドセーフです。同時書き込みに直面しても、スレッドセーフなものもあります。2番目のケースでは、実装に依存します-現在のスレッドで必要に応じて接続などを取得しますか、それとも呼び出し間で共有されるものを使用しますか? 3 番目のケースでは、別のスレッドが代わりに取得したアイテムを「失う」ことを許容できると見なさない限り、スレッドセーフではありません。まあ、「その他」は「その他」のとおりです。
以上のことから、スレッドセーフを保証するものはありませんが、必要な程度のスレッドセーフを提供する他のコンポーネントと一緒に使用すると、十分な程度のスレッドセーフを提供するものがあります。それを得る。
考えられるすべての用途に直面して、100% スレッドセーフですか? いいえ、何もあなたにそれを与えません。実際には、データ型はスレッドセーフではなく、特定のグループの操作のみです。データ型を「スレッドセーフ」と記述する場合、そのメンバーメソッドとプロパティはすべてスレッドセーフであると言い、メソッドまたはプロパティをスレッドセーフと記述すると、それ自体がスレッドセーフであるため、スレッドセーフ操作グループの一部になることができますが、スレッドセーフ操作のすべてのグループがスレッドセーフであるとは限りません。
この種のアプローチを実装したい場合は、呼び出されたオブジェクト (拡張機能ではなくメンバーの場合) とパラメーターに基づいて、オブジェクトを作成するメソッドまたは拡張機能を作成する必要がありますが、変更はしません。
Enumerable.Select
議論のために、メソッドの 2 つの個別の実装を考えてみましょう。
public static IEnumerable<TResult> SelectRightNow<TSource, TResult>(
this IEnumerable<TSource> source,
Func<TSource, TResult> selector)
{
var list = new List<TResult>();
foreach(TSource item in source)
list.Add(selector(item));
return list;
}
public static IEnumerable<TResult> SelectEventually<TSource, TResult>(
this IEnumerable<TSource> source,
Func<TSource, TResult> selector)
{
foreach(TSource item in source)
yield return selector(item);
}
どちらの場合も、メソッドは何らかの方法で の内容に基づいた新しいオブジェクトをすぐに返しますsource
。source
ただし、linq から得られる一種の遅延反復があるのは 2 番目だけです。最初の方法は、実際には 2 番目の方法よりもいくつかのマルチスレッドの状況にうまく対処できますが、厄介なことになります (たとえば、同時実行管理の一部としてロックを保持しながらコピーを取得したい場合は、ロックを保持しながらコピーを取得することによって行いますロック、他のものの真っ只中ではありません)。
どちらの場合でも、提供できるスレッドセーフの鍵となるのは、返されるオブジェクトです。1 つ目は、その結果に関するすべての情報を取得しているため、単一のスレッドに対してローカルでのみ参照される限り、スレッドセーフです。2 番目には、これらの結果を生成するために必要な情報が含まれているため、単一のスレッドに対してローカルでのみ参照される限り、ソースへのアクセスはスレッドセーフであり、呼び出しFunc
はスレッドセーフです。最初に最初のものを作成します)。
要約すると、ソースと s のみを参照するオブジェクトを生成するメソッドがある場合、ソースとFunc
s と同じくらいスレッドセーフになりますFunc
が、安全ではありません。
*メモ化は、最適化として、外部からは見えない副作用をもたらします。s またはそれらが呼び出すもの (getter など) によって使用される場合Func
、スレッドセーフを可能にするには、メモ化をスレッドセーフな方法で実装する必要があります。