A deadlock is what occurs when two or more threads are waiting for eachother to complete or to release a resource in such a way that they wait forever.
If thread1 holds a lock on resource A and is waiting for resource B to be released while thread2 holds resource B and is waiting for resource A to be released, they are deadlocked.
Clicking button1 for the following example code will cause your application to get into aforementioned deadlocked state and hang
private void button_Click(object sender, EventArgs e)
{
DeadlockWorkers workers = new DeadlockWorkers();
workers.StartThreads();
textBox.Text = workers.GetResult();
}
private class DeadlockWorkers
{
Thread thread1, thread2;
object resourceA = new object();
object resourceB = new object();
string output;
public void StartThreads()
{
thread1 = new Thread(Thread1DoWork);
thread2 = new Thread(Thread2DoWork);
thread1.Start();
thread2.Start();
}
public string GetResult()
{
thread1.Join();
thread2.Join();
return output;
}
public void Thread1DoWork()
{
Thread.Sleep(100);
lock (resourceA)
{
Thread.Sleep(100);
lock (resourceB)
{
output += "T1#";
}
}
}
public void Thread2DoWork()
{
Thread.Sleep(100);
lock (resourceB)
{
Thread.Sleep(100);
lock (resourceA)
{
output += "T2#";
}
}
}
}
To avoid being deadlocked this way, one can use Monitor.TryEnter(lock_object, timeout_in_milliseconds) to check if a lock is held on an object already. If Monitor.TryEnter does not succeed in acquiring a lock on lock_object before timeout_in_milliseconds, it returns false, giving the thread a chance to release other held resources and yielding, thus giving other threads a chance to complete as in this slightly modified version of the above:
private void button_Click(object sender, EventArgs e)
{
MonitorWorkers workers = new MonitorWorkers();
workers.StartThreads();
textBox.Text = workers.GetResult();
}
private class MonitorWorkers
{
Thread thread1, thread2;
object resourceA = new object();
object resourceB = new object();
string output;
public void StartThreads()
{
thread1 = new Thread(Thread1DoWork);
thread2 = new Thread(Thread2DoWork);
thread1.Start();
thread2.Start();
}
public string GetResult()
{
thread1.Join();
thread2.Join();
return output;
}
public void Thread1DoWork()
{
bool mustDoWork = true;
Thread.Sleep(100);
while (mustDoWork)
{
lock (resourceA)
{
Thread.Sleep(100);
if (Monitor.TryEnter(resourceB, 0))
{
output += "T1#";
mustDoWork = false;
Monitor.Exit(resourceB);
}
}
if (mustDoWork) Thread.Yield();
}
}
public void Thread2DoWork()
{
Thread.Sleep(100);
lock (resourceB)
{
Thread.Sleep(100);
lock (resourceA)
{
output += "T2#";
}
}
}
}
Note that this workaround relies on thread2 being stubborn about its locks and thread1 being willing to yield, such that thread2 always take precedence. Also note that thread1 has to redo the work it did after locking resource A, when it yields. Therefore be careful when implementing this approach with more than one yielding thread, as you'll then run the risk of entering a so-called livelock - a state which would occur if two threads kept doing the first bit of their work and then yield mutually, starting over repeatedly.