首页 > 解决方案 > 在 WPF 应用程序中锁定字典访问

问题描述

我正在开发一个旧的大型 WPF 应用程序。客户报告了一个错误,他们能够重现,但我不能。应用程序中有一个类,如下所示:

public static class PermissionProvider
{
    private static Dictionary<string, bool> Permissions;

    public static void Init()
    {
        Permissions = new Dictionary<string, bool>();
    }
    
    private static object _lock = new object();
    public static bool HasPermission(string permission)
    {
        if (string.IsNullOrEmpty(permission)) return false;

        lock (_lock)
        {
            if (Permissions.ContainsKey(permission)) return Permissions[permission];
            var hasPermission = true; // Expensive call a third party module to check user permissions.
            Permissions.Add(permission, hasPermission);
            return hasPermission;
        }
    }
}

根据客户提供的日志文件,该行Permissions.Add(permission, hasPermission)抛出了一个ArgumentException(key already exists)。这对我来说没有意义;代码检查同一锁内的钥匙。

根据测试运行,所有调用HasPermission似乎都是从主线程进行的。该程序Dispatcher.BeginInvoke在地方使用,但我的理解是锁定甚至不是必需的。字典是私有的,不能从其他任何地方访问。

在什么情况下会发生这种异常?


我的第一个想法是客户正在运行旧版本的应用程序,但事实证明这个类只是在最新版本中添加的。

这个特殊的异常应该很容易通过更改 to 来避免Permissions.Add(permission, hasPermission)Permissions[permission] = hasPermission但我更愿意先了解它为什么会发生。

标签: c#wpfmultithreading

解决方案


这是可能的,但如果没有完整的源代码就很难说。

昂贵的电话

var hasPermission = true; // Expensive call a third party module to check user permissions.

可以做一些再次调用的事情HasPermission()。因此,相同的线程将进入

lock (_lock) { ... }

再次(这是允许的),可能添加许可,离开锁,离开方法并返回到HasPermission()它来自的地方,再次添加相同的密钥。

这可能需要在您的客户处进行生产调试。如果您对此不熟悉并且可以说服您的客户暂时替换受影响的 DLL(让他创建一个备份副本),您可以尝试以下操作:

lock (_lock) 
{
    var stack = Environment.StackTrace;
    if (stack.Split(new []{nameof(HasPermission)}, StringSplitOptions.None).Length> 2) throw new Exception("I should not be in here twice");
    ...
}

这应该会使应用程序崩溃(除非某个地方的通用 catch 块),其调用堆栈具有两次受影响的方法,因此您可以分析第二次调用的来源。在这种情况下做任何你想做的事情:生成故障转储,分析你的日志,......

生成堆栈跟踪非常昂贵,因此这将改变时间并因此可能使问题消失。但是,消失的问题并不是固定的问题。


推荐阅读