データを含むクラスを書いています。データのクエリを可能にするメソッドを公開しますが、データは外部ソース(Webサービスなど)からも更新されます。
すべてのメソッドは、デフォルトのタスクスケジューラを使用してワーカースレッドで開始されるタスクを公開します。したがって、クラスの構造は次のとおりです。
public class A
{
private object _myLock;
public Task<int> GetSomeNumber(int aParameter)
{
return Task.Run(() =>
{
lock (_myLock)
{
int result = 0;
// calculate the result...
// ...
return result;
}
});
}
public Task<string> GetSomeText(int aParameter)
{
return Task.Run(() =>
{
lock (_myLock)
{
string result = "";
// calculate the result...
// ...
return result;
}
});
}
}
メソッドは、データ自体が更新されている間に複数回呼び出される可能性があるため、共有データを必要とする計算は、ロックステートメントで囲まれています。問題は、このコードが、タスクが実行されるスレッドをブロックすることです。もちろん、これらはUIスレッドではないため、UIは応答性を維持しますが、スレッドを消費し、ブロックされたままにします。スレッドプール内のワーカースレッドは、可能な限りそのままにしておくことをお勧めします。
共有データがスレッドセーフであると同時にタスクがブロックされないように、このコードを記述できる方法(ある種のベストプラクティス)はありますか?ロックを待つ代わりに、タスクが他の何かが完了するのを待つ必要がある場合(タスク)、継続タスクを作成して返すためにContinueWith
を使用して、スケジュールされるようにします最初のタスクが完了したときに実行します。ロックを使用して同じことを行う方法はありますか?そのため、ロックが使用可能なときにのみタスクがスケジュールされますか?
更新私はいくつかの読み取りを行い、共有データの同期を可能にするいくつかの可能なオプションについて知りました。これらには、SemaphoreSlim
、AsyncLock
、およびConcurrentExclusiveSchedulerPair
が含まれます。
読み取りロックと同時に実行できる「読み取り専用」タスクメソッドと、書き込みロックを必要とする「書き込み」タスクメソッドを提供するサービスクラスを作成するベストプラクティスの推奨事項を知りたいです。
この回答は、コメントに記載されているユースケースに関する追加情報に基づいています。
グラフノードを提供するWebサービスがあります。グラフの一部のローカルコピーを保持し、非同期APIを公開してグラフをクエリします。クエリを実行する領域がキャッシュ内にある場合は、単純に計算して結果を返します。それ以外の場合は、不足している部分のダウンロードを開始し、計算を実行して結果を返します。また、バックグラウンドには、サーバーでノードの変更をポーリングし、変更をローカルキャッシュに適用する定期的なタスクがあります。
要求を満たすために必要なデータが単に利用できない(たとえば、ダウンロードもキャッシュもされていない)ために要求をすぐに完了できない場合は、いくつかの選択肢しかないことを理解する必要があります。
TryGet
パターンに似ていますが、タスクは成功しません。上記の各オプションには、独自の「ベストプラクティス」のセットとTPLとの統合パターンがあります。ここでそれらすべてを議論するのは多すぎるでしょう。
必要なヘルプ:これらのオプションのベストプラクティスをカバーする優れたオンラインリソースを誰かが知っている場合は、コメントとして投稿してください。
アプリケーションの要件を考慮して、リーダーのブロックが唯一の選択肢であると判断した場合は、次のことに注意してください。
並行リーダーがスタックしないようにしたい場合(障害物がないため)、いくつかの選択肢があると思います。
複数のバッファリング
グラフの同時更新アルゴリズム
正確な詳細は、アプリケーションに必要な操作とアルゴリズムによって異なります。
それらのいくつかは、ハードウェア対応のアトミック命令を使用して、ロックフリーである場合があります。他のユーザーは、さまざまな種類のロックを使用する可能性がありますが、リーダーがブロックされる可能性または時間の長さを最小限に抑えようとする場合があります。
既存のライブラリを使用するか、独自のライブラリを実装することができます。
同時グラフ更新アルゴリズムに慣れていないので、あまり提案できません。
書き込みよりも読み取りの方が多いと予想される場合、一般的に安全な賭けは ReaderWriterLockSlim です。これにより、同時スレッドがデータを読み取ることができますが、一度に書き込みできるスレッドは1つだけです。
あなたは並行リストと辞書について言及しましたが、 ConcurrentDictionary <、> がこの状況で機能しない理由はありますか?私はそれが非常に高性能であり、ほとんどの状況で対処するのがはるかに面倒ではないことを発見しました。
一部のパフォーマンスが重要なソリューションでは、Interlocked.CompareExcgangeは非常に適切に機能しますが、これは同期する必要があるデータによって異なります。CompareExchangeは数値型で適切に機能しますが、参照ベースの変数は避けます。
どうですか:
// any child object should freeze when this object freezes...
interface IFreazableCloanable
{
bool IsFrozen { get; }
// Any further attempts to change the object should throw an Exception
void Freeze();
// Creates mutable Clone of the object
IFreazableCloanable DeepCloneMutable();
}
class SyncedObject<T> where T : class, IFreazableCloanable
{
SemaphoreSlim _writersLock = new SemaphoreSlim(0, 1);
volatile T _internal;
public SyncedObject(T thing) // if writes are unsynced then only the last one counts.
{
_internal = (T)thing.DeepCloneMutable();
}
public T Read()
{
T result = _internal;
result.Freeze();
return result;
}
public void Write(Func<T, T> writer)
{
_writersLock.Wait();
T temp = (T)_internal.DeepCloneMutable();
temp = writer(temp);
_internal = temp;
_writersLock.Release();
}
}
class Data : IFreazableCloanable
{
private string _data = "lala";
public string MyData
{
get
{
return _data;
}
set
{
if (IsFrozen)
{
throw new Exception("Very Bad");
}
_data = value;
}
}
public bool IsFrozen
{
get;
private set;
}
public void Freeze()
{
IsFrozen = true;
}
public IFreazableCloanable DeepCloneMutable()
{
return new Data();
}
}