C# - 解决潜在的死锁解决方案
C# - Solving potential deadlock solution
我有一个 LongOperationHelper,我会在每次可能的长时间操作时激活它。
它显示一个半透明层,在操作结束之前不允许任何点击,并有一个旋转控件来指示进度。
看起来像这样(缺少一些业务逻辑,但我认为这个想法很清楚):
编辑:(添加了实际需要锁定的常见状态的缺失代码 - 这更像是有问题的代码)
(我的解决方案发布在下面的答案中)
public static class LongOperationHelper
{
private static object _synchObject = new object();
private static Dictionary<string, int> _calls = new Dictionary<string, int>();
private static Action<string> DisplayLongOperationRequested;
private static Action<string> StopLongOperationRequested;
public static void Begin(string messageKey)
{
lock (_synchObject)
{
if (_calls.ContainsKey(messageKey))
{
_calls[messageKey]++;
}
else
{
_calls.Add(messageKey, 1);
DispatcherHelper.InvokeIfNecesary(() =>
{
//Raise event for the MainViewModel to display the long operation layer
DisplayLongOperationRequested?.Invoke(messageKey);
});
}
}
}
public static void End(string messageKey)
{
lock (_synchObject)
{
if (_calls.ContainsKey(messageKey))
{
if (_calls[messageKey] > 1)
{
_calls[messageKey]--;
}
else
{
_calls.Remove(messageKey);
DispatcherHelper.InvokeIfNecesary(() =>
{
//Raise event for the MainViewModel to stop displaying the long operation layer
StopLongOperationRequested?.Invoke(messageKey);
});
}
}
else
{
throw new Exception("Cannot End long operation that has not began");
}
}
}
}
正如您可能看到的那样,如果:
- 有人从非 UI 线程调用 Begin。
- 入锁
- 有人从 UI 线程调用 Begin 或 End 并被锁定
- 第一个 Begin 调用尝试调度到 UI 线程。
结果:死锁!
我想让这个 Helper 线程安全,这样任何线程都可以在任何给定时间调用 Begin 或 End,有兴趣看看是否有任何已知模式,任何想法?
谢谢!
不要锁定整个方法。仅在您触摸需要它的字段时锁定,并在您完成后立即解锁。每次触摸这些字段时锁定和解锁。否则,你最终会遇到这样的死锁。
也可以考虑使用ReaderWriterLockSlim
,区分读锁和写锁。它允许多个线程同时读取,但在获取写锁时将所有人锁在外面。该文档中有一个关于如何使用它的示例。
"UI thread" 的全部目的就是避免像这样的同步。事实上,所有 UI 代码都需要在单个线程上 运行,这意味着根据定义,它不能同时 运行。您无需使用锁来使您的 UI 代码 运行 原子地 因为它全部 运行 在单个线程上 .
编写 UI 要求程序员自己锁定的代码非常困难且容易出错,因此整个框架的设计理念是期望人们(正确地)这样做是不合理的,并且简单地强制所有 UI 代码进入单个线程要容易得多,这样就不需要其他同步机制了。
这里是 "Deadlock free" 代码:
我已将 UI 线程的调度重新定位到锁外。
(有人还能看出这里存在潜在的死锁吗?)
public static class LongOperationHelper
{
private static object _synchObject = new object();
private static Dictionary<string, int> _calls = new Dictionary<string, int>();
private static Action<string> DisplayLongOperationRequested;
private static Action<string> StopLongOperationRequested;
public static void Begin(string messageKey)
{
bool isRaiseEvent = false;
lock (_synchObject)
{
if (_calls.ContainsKey(messageKey))
{
_calls[messageKey]++;
}
else
{
_calls.Add(messageKey, 1);
isRaiseEvent = true;
}
}
//This code got out of the lock, therefore cannot create a deadlock
if (isRaiseEvent)
{
DispatcherHelper.InvokeIfNecesary(() =>
{
//Raise event for the MainViewModel to display the long operation layer
DisplayLongOperationRequested?.Invoke(messageKey);
});
}
}
public static void End(string messageKey)
{
bool isRaiseEvent = false;
lock (_synchObject)
{
if (_calls.ContainsKey(messageKey))
{
if (_calls[messageKey] > 1)
{
_calls[messageKey]--;
}
else
{
_calls.Remove(messageKey);
isRaiseEvent = true;
}
}
else
{
throw new Exception("Cannot End long operation that has not began");
}
}
//This code got out of the lock, therefore cannot create a deadlock
if (isRaiseEvent)
{
DispatcherHelper.InvokeIfNecesary(() =>
{
StopLongOperationRequested?.Invoke(messageKey);
});
}
}
}
我有一个 LongOperationHelper,我会在每次可能的长时间操作时激活它。 它显示一个半透明层,在操作结束之前不允许任何点击,并有一个旋转控件来指示进度。
看起来像这样(缺少一些业务逻辑,但我认为这个想法很清楚):
编辑:(添加了实际需要锁定的常见状态的缺失代码 - 这更像是有问题的代码)
(我的解决方案发布在下面的答案中)
public static class LongOperationHelper
{
private static object _synchObject = new object();
private static Dictionary<string, int> _calls = new Dictionary<string, int>();
private static Action<string> DisplayLongOperationRequested;
private static Action<string> StopLongOperationRequested;
public static void Begin(string messageKey)
{
lock (_synchObject)
{
if (_calls.ContainsKey(messageKey))
{
_calls[messageKey]++;
}
else
{
_calls.Add(messageKey, 1);
DispatcherHelper.InvokeIfNecesary(() =>
{
//Raise event for the MainViewModel to display the long operation layer
DisplayLongOperationRequested?.Invoke(messageKey);
});
}
}
}
public static void End(string messageKey)
{
lock (_synchObject)
{
if (_calls.ContainsKey(messageKey))
{
if (_calls[messageKey] > 1)
{
_calls[messageKey]--;
}
else
{
_calls.Remove(messageKey);
DispatcherHelper.InvokeIfNecesary(() =>
{
//Raise event for the MainViewModel to stop displaying the long operation layer
StopLongOperationRequested?.Invoke(messageKey);
});
}
}
else
{
throw new Exception("Cannot End long operation that has not began");
}
}
}
}
正如您可能看到的那样,如果:
- 有人从非 UI 线程调用 Begin。
- 入锁
- 有人从 UI 线程调用 Begin 或 End 并被锁定
- 第一个 Begin 调用尝试调度到 UI 线程。
结果:死锁!
我想让这个 Helper 线程安全,这样任何线程都可以在任何给定时间调用 Begin 或 End,有兴趣看看是否有任何已知模式,任何想法?
谢谢!
不要锁定整个方法。仅在您触摸需要它的字段时锁定,并在您完成后立即解锁。每次触摸这些字段时锁定和解锁。否则,你最终会遇到这样的死锁。
也可以考虑使用ReaderWriterLockSlim
,区分读锁和写锁。它允许多个线程同时读取,但在获取写锁时将所有人锁在外面。该文档中有一个关于如何使用它的示例。
"UI thread" 的全部目的就是避免像这样的同步。事实上,所有 UI 代码都需要在单个线程上 运行,这意味着根据定义,它不能同时 运行。您无需使用锁来使您的 UI 代码 运行 原子地 因为它全部 运行 在单个线程上 .
编写 UI 要求程序员自己锁定的代码非常困难且容易出错,因此整个框架的设计理念是期望人们(正确地)这样做是不合理的,并且简单地强制所有 UI 代码进入单个线程要容易得多,这样就不需要其他同步机制了。
这里是 "Deadlock free" 代码: 我已将 UI 线程的调度重新定位到锁外。
(有人还能看出这里存在潜在的死锁吗?)
public static class LongOperationHelper
{
private static object _synchObject = new object();
private static Dictionary<string, int> _calls = new Dictionary<string, int>();
private static Action<string> DisplayLongOperationRequested;
private static Action<string> StopLongOperationRequested;
public static void Begin(string messageKey)
{
bool isRaiseEvent = false;
lock (_synchObject)
{
if (_calls.ContainsKey(messageKey))
{
_calls[messageKey]++;
}
else
{
_calls.Add(messageKey, 1);
isRaiseEvent = true;
}
}
//This code got out of the lock, therefore cannot create a deadlock
if (isRaiseEvent)
{
DispatcherHelper.InvokeIfNecesary(() =>
{
//Raise event for the MainViewModel to display the long operation layer
DisplayLongOperationRequested?.Invoke(messageKey);
});
}
}
public static void End(string messageKey)
{
bool isRaiseEvent = false;
lock (_synchObject)
{
if (_calls.ContainsKey(messageKey))
{
if (_calls[messageKey] > 1)
{
_calls[messageKey]--;
}
else
{
_calls.Remove(messageKey);
isRaiseEvent = true;
}
}
else
{
throw new Exception("Cannot End long operation that has not began");
}
}
//This code got out of the lock, therefore cannot create a deadlock
if (isRaiseEvent)
{
DispatcherHelper.InvokeIfNecesary(() =>
{
StopLongOperationRequested?.Invoke(messageKey);
});
}
}
}