ここでdbasemanの答えを拡張したいと思います。具体的には、彼のDoWorkIfNotBusy
方法。
これは(競合状態の可能性があるため)悪い例だと思います。正しい方法は次のようになります。
private static readonly object lockobj = new object();
public bool DoWorkIfNotBusy()
{
bool lockWasTaken = false;
var temp = lockobj;
try
{
Monitor.TryEnter(temp, ref lockWasTaken);
if (lockWasTaken) // This crucial test was missing! (Added 2021-04-08)
{
//Do work here..
}
}
finally
{
if (lockWasTaken) Monitor.Exit(temp);
}
return lockWasTaken;
}
2021-04-08を更新:現在のスレッドがロックの取得に成功したかどうかを確認するためのテストを追加しました。そうでない場合は、クリティカルセクションのコードを実行しないでください。
更新された2021-04-09:単体テストlockWasTaken
としてのチェックの必要性を示す以下のコード。
[TestClass]
public class DummyTests
{
private static readonly object LockObj = new object();
[TestMethod]
public void TestMonitor()
{
Thread trd1 = StartThread();
Thread.Sleep(100);
Thread trd2 = StartThread();
Thread.Sleep(1000);
Thread trd3 = StartThread();
while (trd1.IsAlive || trd2.IsAlive || trd3.IsAlive)
{
Thread.Sleep(100);
}
}
private Thread StartThread()
{
var thread = new Thread(parameter => this.ThreadTask());
thread.Start(nameof(thread));
Trace.WriteLine($"Started thread {thread.ManagedThreadId}.");
return thread;
}
private void ThreadTask()
{
bool lockAcquired = false;
try
{
Monitor.TryEnter(LockObj, ref lockAcquired);
if (lockAcquired)
{
Trace.WriteLine($"Lock acquired by thread {Thread.CurrentThread.ManagedThreadId}.");
Thread.Sleep(1000);
}
else
{
Trace.WriteLine($"Lock denied to thread {Thread.CurrentThread.ManagedThreadId}.");
}
}
finally
{
if (lockAcquired)
{
Monitor.Exit(LockObj);
Trace.WriteLine($"Lock released by thread {Thread.CurrentThread.ManagedThreadId}.");
}
}
}
}
出力は次のようになります。
Started thread 12.
Lock acquired by thread 12.
Started thread 13.
Lock denied to thread 13.
Lock released by thread 12.
Started thread 14.
Lock acquired by thread 14.
Lock released by thread 14.