C#中单元测试线程安全性

3

考虑以下操作:

public Dictionary<string, string> objectDictionary = new Dictionary<string, string>();

source = () =>
{
    if (!objectDictionary.ContainsKey("demo"))
    { objectDictionary.Add("demo", "value"); }
};

这个方法不是线程安全的,因为有可能两个线程同时进入到

public Dictionary<string, string> objectDictionary = new Dictionary<string, string>();

条件。

现在,我正在考虑一种解决方案,测试此方法是否线程安全,因此我想出了以下解决方案:

var isThreadSafe = true;

// Check to see if the action can be considered as thread safe.
try { for (var i = 0; i < 1000; i++) { Parallel.Invoke(() => action(), () => action()); } }
catch (Exception) { isThreadSafe = false; }

问题在于布尔变量 'isThreadSafe' 没有被设置为 false。如果我调试应用程序,那么就会出现与线程安全相关的错误,“已经添加了具有相同键的项”。现在,是否有更好的单元测试方法?

1
问题实际上是为什么这个代码没有捕获到异常。 - Weyland Yutani
你检查了哪些异常被捕获了吗?(如果我没记错的话,可以通过 Visual Studio 的菜单设置某些异常无法被用户捕获) - Thomas
目前您只在并行运行 2 个任务,干扰的概率较小。使用 Parallel.For 可以根据机器资源的可用性并行运行它。我想这应该会有所帮助。 - Sriram Sakthivel
当我使用1000000个并行迭代的“Parallel.For”时,它也无法工作,我想我只能放弃 :) - Complexity
你可能会对微软研究中心的CHESS项目感兴趣。 - Damien_The_Unbeliever
显示剩余3条评论
2个回答

4

在每次迭代开始时,您需要清除字典。否则,无论您运行多少次(100,000次、1000次或1次),在添加项之前只会有一次竞争的机会,而ContainsKey("demo")始终返回false。

解决方案是在每次迭代开始时清除字典:

for (var i = 0; i < 1000; n++)
{
    objectDictionary.Clear();
    Parallel.Invoke(action, action);
}

请注意,Dictionary不是线程安全的,如果从多个线程中频繁地调用任何非线程安全的类,可能会导致程序挂起而不是异常。虽然我怀疑在使用Add和ContainsKey时不会发生挂起。


3
测试竞争条件的代码将会极其困难,特别是在自动化测试方面。竞争条件的定义就是不会持续地可重现。此外,可能会出现各种问题。可能会抛出许多不同类型的异常,但也有可能会在您的集合中显示垃圾数据、丢失数据或出现各种其他类型的奇怪行为。
竞争条件也非常依赖于各种操作的时间安排。当您调试程序并逐步执行时,您最终人为地(而且通常是显著地)改变了这些时间安排,以一种可能会改变各种竞争条件结果的方式。调试程序的行为本身可能会创建或隐藏不同的错误,这正是您现在所看到的情况。
要准确地测试所有可能的错误行为,您需要首先预测可能出错的情况,这意味着需要理解所有可能的不支持行为。这就是处理多线程程序如此困难的原因。

谢谢。我知道这很难测试,但我想尽可能覆盖多种情况。 - Complexity
1
@复杂性 实际上全面测试可能的执行路径的大部分是不可能的。即使是单线程代码,要覆盖复杂程序中可能的执行排列的显著百分比也很困难,但当您有多个线程并且每个操作的顺序都是变化的可能位置时,真正有机会发现您没有预料到的错误非常非常低。实际上,您需要能够有效地推理编写程序。 - Servy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接