マルチスレッドコンテキストでメソッドを排他的にする方法は?
-
02-07-2019 - |
質問
排他的な方法で実行するメソッドがあります。基本的に、これはメソッドがタイマーによって定期的に呼び出されるマルチスレッドアプリケーションですが、ユーザーアクションによって手動でトリガーすることもできます。
例を見てみましょう:
-
タイマーが経過すると、メソッドは と呼ばれます。タスクには数分かかる場合があります 秒。
-
直後に、ユーザーはいくつかをクリックします ボタン、トリガーする必要があります 同じタスク:BAM。何もしない メソッドが既に実行されているため。
次のソリューションを使用しました:
public void DoRecurentJob()
{
if(!Monitor.TryEnter(this.lockObject))
{
return;
}
try
{
// Do work
}
finally
{
Monitor.Exit(this.lockObject);
}
}
lockObject
が次のように宣言されている場所:
private readonly object lockObject = new object();
編集:このメソッドを保持するオブジェクトのインスタンスは1つだけなので、ロックオブジェクトを非静的に更新しました。
それを行うより良い方法はありますか?それとも、これは何らかの理由で間違っているのでしょうか?
解決
メソッドを並行して実行しないことに関心がある場合、これは合理的に見えます。タイマーがMonitor.Exit()を実行してから0.5マイクロ秒後にボタンを押したとすると、すぐに実行を停止することはできません。
また、ロックオブジェクトを読み取り専用の静的オブジェクトとして使用することも理にかなっています。
他のヒント
Mutex
または Semaphore
をクロスプロセスで使用したい場合(パフォーマンスがわずかに低下します)、または以下の数値を設定する必要がある場合も使用できますコードの一部を実行する許可された同時スレッドの1つ。
機能する他のシグナル伝達構造がありますが、例はトリックを実行しているように見え、シンプルで簡単な方法です。
マイナーnit:lockObject変数が静的な場合、" this.lockObject"コンパイルすべきではありません。また、これはインスタンスメソッドではありますが、タイプ全体の動作も明確になっているということは少し奇妙に感じます(少なくとも文書化する必要があります)。インスタンスをパラメーターとして使用する静的メソッドにすることもできますか?
実際にインスタンスデータを使用しますか?そうでない場合は、静的にします。もしそうなら、少なくともインスタンスで作業をしたかどうかを示すブール値を返す必要があります-特定のデータで作業をしたい状況を想像するのは難しいと思いますが、私はしません同様の作業が別のデータで実行されていたため、その作業が実行されない場合は注意してください。
動作するはずですが、少し奇妙に感じます。私は、手動ロックを使用するのが一般的に好きではありません。それは、間違えやすいからです-しかし、これは大丈夫です。 (" if"と" try"の間の非同期例外を考慮する必要がありますが、それらは問題ではないと思われます-CLRによって行われた正確な保証を思い出せません。)
Microsoft 推奨は、 lock ステートメント。よりクリーンなレイアウトが得られ、あらゆる状況でロックが解除されることが保証されます。
public class MyClass
{
// Used as a lock context
private readonly object myLock = new object();
public void DoSomeWork()
{
lock (myLock)
{
// Critical code section
}
}
}
アプリケーションがMyClassのすべてのインスタンスにまたがるロックを必要とする場合、ロックコンテキストを静的フィールドとして定義できます。
private static readonly object myLock = new object();
コードは問題ありませんが、意図をより良く伝えるため、メソッドを静的に変更することに同意します。クラスのすべてのインスタンスに同期的に実行されるメソッドがありますが、そのメソッドは静的ではありません。
静的同期メソッドは常に保護またはプライベートにでき、クラスのインスタンスにのみ表示されることを忘れないでください。
public class MyClass
{
public void AccessResource()
{
OneAtATime(this);
}
private static void OneAtATime(MyClass instance)
{
if( !Monitor.TryEnter(lockObject) )
// ...
これは良い解決策ですが、静的ロックにはあまり満足していません。今はロックを待っていないので、デッドロックに悩まされることはありません。ただし、ロックをあまりにも見えるようにすると、次回このコードを編集する必要があるときに、簡単にトラブルに巻き込まれる可能性があります。また、これは非常にスケーラブルなソリューションではありません。
通常、クラスの複数のスレッドのプライベートインスタンス変数によるアクセスから保護しようとするすべてのリソースを作成し、プライベートインスタンス変数としてもロックします。そうすれば、スケーリングが必要な場合に複数のオブジェクトをインスタンス化できます。
これを行うより宣言的な方法は、アクセスを同期するメソッドのMethodImplOptions.Synchronized 指定子:
[MethodImpl(MethodImplOptions.Synchronized)]
public void OneAtATime() { }
ただし、この方法はいくつかの理由で推奨されません。そのほとんどはこちらおよびこちら。あなたがそれを使用したいと思わないように、私はこれを投稿しています。 Javaでは、 synchronized
はキーワードであるため、スレッド化パターンを確認するときに出てくることがあります。
同様の要件があり、長期実行プロセスが再度要求された場合、現在のサイクルが完了した後に別のサイクルを実行するためにキューに入れる必要があるという要件が追加されています。これに似ています:
private queued = false;
private running = false;
private object thislock = new object();
void Enqueue() {
queued = true;
while (Dequeue()) {
try {
// do work
} finally {
running = false;
}
}
}
bool Dequeue() {
lock (thislock) {
if (running || !queued) {
return false;
}
else
{
queued = false;
running = true;
return true;
}
}
}