在ToString()方法中使用lock()进行收集

时间:2016-05-25 12:26:06

标签: c# .net multithreading

当我被覆盖的ToString()方法使用其中一个实例的集合时,我遇到了自己的情况。

public override string ToString()
{
    string returnStr = "testString";

    lock (_sync)
    {
        returnStr = $"{returnStr}: {string.Join(",", _testList)}";  // where _testList is a List<string> in the class's scope
    }

    return returnStr;
}

由于我们讨论的是多线程应用程序,我很想知道,因为我必须在lock()方法的主体中使用ToString(),以避免在我的返回字符串是正在生成。当我在集合上进行修改时,同一个对象(_sync)当然被锁定了 但是,这个方法也被其他几个进程使用,例如Visual Studio的调试器以及谁知道还有什么,因为这个方法是从Object类本身继承的。
例如:我担心它可能会更频繁地被调用(通过框架或其他任何东西),并且必须使用锁定(这可能导致性能损失),或者它可能当我在一个糟糕的时刻进行调试时会导致死锁。

问题:
我是否应该关心这种情况,因为它可能会导致问题,或者在ToString()(以及.net类型的其他继承方法)中使用对象锁定是否可以?

为了实现同样的目标,还有更好的替代解决方案吗?

注意:我每次修改时都在考虑从集合中生成所需的字符串(在锁定内部,正在被操作),所以我将集合的字符串格式准备好了在ToString()方法本身中连接。我想这对于性能会更好,因为string.Join()的过程不必在ToString()的每次调用时运行,但我真的很想知道更多关于这种情况。

谢谢!

4 个答案:

答案 0 :(得分:1)

在这种情况下,您不必担心死锁。死锁需要至少2个锁,而您只有一个。 List类本身不使用任何锁,也不使用string.Join或string.Format。

这是否是性能问题完全取决于您提到的其他应用程序如何使用它。 Visual Studio调试器不会经常调用ToString,只有在调试器中暂停时才会调用!这不是问题。

至于你提到的其他应用程序,你只能通过分析来了解。

答案 1 :(得分:1)

在线程安全方面,ToString没有特殊规则。线程安全是程序的全局属性。您必须理解并控制同时访问数据的所有内容。

如果有ToString可能与写入其访问的数据同时发生,则必须进行适当的同步。在存在并发写入的情况下从List读取特别不安全。

由于ToString内的关键区域仅使用一个锁并且不阻塞,因此死锁的风险很小。一旦锁定进入,保证最终退出。这是锁定的一个很好的设计原则。

也许您可以使用无锁模式,例如不可变集合?或者也许你可以使你的整个对象不可变。

答案 2 :(得分:1)

你真的需要/经常使用ToString()吗? 您的方案似乎更像是一种业务逻辑,我不会将其与ToString()实现混淆。

我会选择:GetSnapshotRepresentation()使用锁和整个难题并让ToString()返回非常轻量级的东西(例如id,type,something immutable等)

将锁和重型机械放入ToString()如果拨打ToString()您不想要/不需要的话,您将阻止作家。

答案 3 :(得分:0)

你不必担心死锁,因为正如@DavidJ所说,只有一个锁就不会发生这种情况。但是你应该关注race conditions

我们假设除了ToString方法之外,您还有一种方法可以删除列表中的所有其他项目。

for(int i = _testList.Count - 1; i >= 0; i -= 2)
{
    _testList.RemoveAt(i);
}

由于您的应用程序是多线程的,因此可以调用ToString(),而另一个线程位于此循环的中间。您可能认为需要锁定对列表的所有访问,如下所示:

for(int i = _testList.Count - 1; i >= 0; i -= 2)
{
    lock (_sync) {
        _testList.RemoveAt(i);
    }
}

这里的问题是:假设你的循环已经迭代了几次但在它完成之前被中断了。如果中断循环的线程调用ToString(),则ToString()方法将输出一个处于不一致状态的列表。从其他线程访问列表不应该在循环内部发生。你实际上必须把锁放在循环之外,如下所示:

lock(_sync) {
    for(int i = _testList.Count - 1; i >= 0; i -= 2)
    {
        _testList.RemoveAt(i);
    }
}

我在这里要说明的是,多线程代码没有简单的规则。只要代码的其他部分也是线程安全的,您的ToString()方法就是线程安全的。