我有一个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");
}
}
}
}
如您所见,如果存在以下情况,则可能存在死锁:
结果:死锁!
我想使此Helper线程安全,以便任何线程都可以在任何给定时间调用Begin或End,以查看是否有任何已知的模式,思路?
谢谢!
答案 0 :(得分:1)
不要锁定整个方法。仅在您触摸需要的字段时锁定,并在完成后立即解锁。每次触摸这些字段时都进行锁定和解锁。否则,您将最终陷入这种僵局。
您还可以考虑使用ReaderWriterLockSlim
,它可以区分读锁和写锁。它允许多个线程同时读取,但是在进行写锁定时将所有人锁定。在该文档中有一个有关如何使用它的示例。
答案 1 :(得分:0)
拥有“ UI线程”的全部目的是避免完全像这样进行同步。所有UI代码都需要在单个线程上运行的事实意味着,根据定义,它不能同时运行。您不必使用锁来使您的UI代码自动运行,因为它们都在单个线程上运行。
编写要求程序员自行锁定的UI代码非常困难且容易出错,因此整个框架的设计都是基于这样的想法:期望人们(正确地)这样做是不合理的,并且简单起来要容易得多强制所有UI代码进入单个线程,而无需其他同步机制。
答案 2 :(得分:0)
这是“无死锁”代码: 我已经将对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);
});
}
}
}