C# 在ToString()方法中对集合使用lock()
我遇到了这样一种情况,当我重写的C# 在ToString()方法中对集合使用lock(),c#,.net,multithreading,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
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;
}
public重写字符串ToString()
{
string returnStr=“testString”;
锁定(同步)
{
returnStr=$“{returnStr}:{string.Join(“,”,_testList)}”;//其中_testList是类作用域中的列表
}
返回返回str;
}
因为我们讨论的是多线程应用程序,所以我想知道,因为我必须在ToString()
方法的主体中使用lock()
,以避免在生成返回字符串时修改集合。当我对集合进行修改时,同一个对象(\u sync
)当然被锁定。但是,由于此方法是从
对象
类本身继承的,因此其他几个进程也在使用此方法,例如Visual Studio的调试器和谁知道还有什么
例如:我担心它可能会在幕后被调用得更频繁(由框架或其他任何东西调用),并且必须使用锁定(这可能会导致性能损失),或者可能会在我在糟糕的时刻调试时导致死锁
问题:我应该关心这种情况,因为它可能会导致问题,还是可以在
ToString()
(以及从.net类型继承的其他方法)中使用对象锁定
为了实现同样的目标,有没有更好的替代方案
注意:我想在每次修改集合时(在锁内,它被操纵的地方)都从集合中生成所需的字符串,因此我准备好将集合的字符串格式连接到ToString()
方法本身中。我想它的性能会更好,因为string.Join()
的进程不必在每次调用ToString()
时都运行,但我真的很想知道更多关于这种情况的信息
谢谢 在这种情况下,您不必担心死锁。死锁至少需要两个锁,而您只有一个。List类本身不使用任何锁,string.Join或string.Format也不使用 这是否是一个性能问题完全取决于您提到的其他应用程序如何使用它。VisualStudio调试器不会经常调用ToString,而且只有在调试器中暂停时才会调用!这不是问题
至于您提到的其他应用程序,您只能通过分析来了解。在线程安全方面,对于
ToString
没有特殊的规则。线程安全是程序的全局属性。您必须理解并控制并发访问数据的所有内容
如果ToString
有可能与对其访问的数据的写入同时发生,则必须进行适当的同步。在存在并发写入的情况下,从列表
中读取尤其不安全
由于ToString
中的关键区域只使用一个锁,并且不阻塞,因此死锁的风险很小。一旦进入锁,保证最终退出。这是一个很好的锁定设计原则
也许您可以使用无锁模式,例如不可变集合?或者您可以使整个对象不可变。您真的需要/经常使用
ToString()
?
您的场景更像是一个业务逻辑,我不会将其与ToString()
实现混淆
我会选择这样的东西:GetSnapshotRepresentation()
,它使用锁和整个难题,让ToString()
返回一些非常轻量级的东西(例如id、类型、不可变的东西等等)
如果你不想/不需要调用
ToString()
,那么把锁和那台沉重的机器放进ToString()
,你就会阻塞你的写入程序。你不必担心死锁,因为正如@DavidJ所说,死锁不会只发生在一个锁上。然而,你应该关心这个问题
假设除了ToString方法之外,还有一个方法可以删除列表中的所有其他项
for(int i = _testList.Count - 1; i >= 0; i -= 2)
{
_testList.RemoveAt(i);
}
由于应用程序是多线程的,可以在另一个线程位于这个循环的中间时调用您的toStrug()。您可能认为需要像下面这样锁定对列表的所有访问:
for(int i = _testList.Count - 1; i >= 0; i -= 2)
{
lock (_sync) {
_testList.RemoveAt(i);
}
}
lock(_sync) {
for(int i = _testList.Count - 1; i >= 0; i -= 2)
{
_testList.RemoveAt(i);
}
}
这里的问题是:假设您的循环已经迭代了几次,但在完成之前就被中断了。如果中断循环的线程调用ToString(),则ToString()方法将输出一个状态不一致的列表。从其他线程访问列表不应该发生在循环内部。实际上,您必须将锁放在循环之外,如下所示:
for(int i = _testList.Count - 1; i >= 0; i -= 2)
{
lock (_sync) {
_testList.RemoveAt(i);
}
}
lock(_sync) {
for(int i = _testList.Count - 1; i >= 0; i -= 2)
{
_testList.RemoveAt(i);
}
}
我在这里试图说明的是,对于多线程代码没有简单的规则。只要代码的其他部分也是线程安全的,那么ToString()方法就是线程安全的