C# 如何正确锁定StringBuilder,使其不会抛出ArgumentOutOfRangeException

C# 如何正确锁定StringBuilder,使其不会抛出ArgumentOutOfRangeException,c#,multithreading,stringbuilder,C#,Multithreading,Stringbuilder,我在stack overflow和其他地方读过多篇文章,都认为StringBuilder不是线程安全的,当从多个线程访问(读/写)时,应该锁定:。我不清楚如何锁定我的Stringbuilder实例,特别是应该添加多少个锁以及在哪里添加锁 我有以下代码(代码片段): 类内存追踪程序 { 私有进程extProc{get;set;} 私有StringBuilder sb{get;set;} 内部int trackByIP(字符串ip) { ... ... sb=新的StringBuilder(); e

我在stack overflow和其他地方读过多篇文章,都认为StringBuilder不是线程安全的,当从多个线程访问(读/写)时,应该锁定:。我不清楚如何锁定我的Stringbuilder实例,特别是应该添加多少个锁以及在哪里添加锁

我有以下代码(代码片段):

类内存追踪程序
{
私有进程extProc{get;set;}
私有StringBuilder sb{get;set;}
内部int trackByIP(字符串ip)
{
...
...
sb=新的StringBuilder();
extProc.OutputDataReceived+=新的DataReceivedEventHandler((s,e)=>sb.Append(e.Data+“\n”);
extProc.ErrorDataReceived+=新的DataReceivedEventHandler((s,e)=>sb.Append(e.Data+“\n”);
extProc.Start();
extProc.PriorityClass=ProcessPriorityClass.RealTime;
...
...
}
字符串getDataWStartEndPttrn(字符串生成器数据、字符串strPttr、字符串endPttr、字符串扩展终止符)
{
字符串s=data.ToString();//=0&&si>=0)
{
字符串s1=s.子字符串(si,se-si);
字符串sTMP=s.Substring(se);
字符串s2=s.Substring(se,sTMP.IndexOf(extendedTerminator));
返回s1+s2;
}
返回“”;
}
放置锁后,我仍然看到抛出相同的错误

class MemoryTracker
{
    private Process extProc    { get; set; }
    private StringBuilder sb   { get; set; }
    private Object thisLock = new Object();


    string getDataWStartEndPttrn(StringBuilder data, string strPttr, string endPttr, string extendedTerminator)
    {
        lock (thisLock)
        {
             string s = data.ToString() ; // <-- STILL THROWS ArgumentOutOfRangeException 
        }
 ...
 ...
类内存追踪程序
{
私有进程extProc{get;set;}
私有StringBuilder sb{get;set;}
私有对象thisLock=新对象();
字符串getDataWStartEndPttrn(字符串生成器数据、字符串strPttr、字符串endPttr、字符串扩展终止符)
{
锁(这个锁)
{
字符串s=data.ToString();//替代解决方案

围绕StringBuilder创建线程安全的包装器使用它而不是当前的。这将在包装器类中封装同步逻辑。我认为这样比锁定使用StringBuilder类的方法要好一点

使用。当获取读卡器锁时,它允许多个线程访问代码块,而当获取写锁时,只允许一个线程访问。简单地说,如果多个线程尝试使用
.ToString()
方法,这将很好,如果是,其他一些线程尝试使用
.Append())
同时,此线程将等待所有其他读取线程(
.ToString()
)完成

这里是非常基本的impl,它应该是一个很好的起点

public class ThreadSafeStringBuilder 
{
   private readonly StringBuilder core = .....
   private readonly ReaderWriterLockSlim sync = ....

   public void Append(String str) {
      try {
         this.sync.EnterWriterLock();
         this.core.Append(str);
      }
      finally {
         this.sync.ExitWriterLock()
      }
    }

  public override ToString() {
    try {
       this.sync.EnterReadLock();
       return this.core.ToString();
    }
    finnaly {
       this.sync.ExitReadLock()
    }
  }
}
现在,您可以使用线程安全包装器,而无需在任何地方同步对它的访问


编辑2017-12-18:评论中的建议。您应该执行一些性能测试。对于简单的场景更合适(检查您的最坏情况,如您希望读取或写入多少线程…等,同时检查最佳情况…等)。对于代码,只需将try、finnaly语句替换为lock语句,并将
.Append()
.ToString()的锁替换为
应该锁定同一个对象。

您还应该锁定2个
sb.Append
语句。这是锁的错误用法。您锁定了特定对象的用法。在这种情况下,您可能希望锁定字符串生成器。您似乎正在将字符串生成器作为参数传入,这意味着锁不起任何作用防止它被其他线程访问。字符串生成器上的每个非线程安全操作都需要锁定(通常是修改sb内容的anthing)。非常感谢,我的想法是,我可能应该同时锁定append()和.toString()调用。但可能一个就足够了(对于appends)?我相信追加应该足够了。不确定您是否也需要担心在写入时防止读取。是的,您错过了锁定的很多要点(我想说的是全部要点)。创建一个锁对象(或锁字符串生成器本身),并像这样锁定:
newdatareceivedeventhandler((s,e)=>{lock(_sbLock){sb append(e.Data+“\n”);})
,不像你现在这样做。ReaderWriterLock只有在读的时候有很多争用,写的时候很少的情况下才是有效的。在OP的场景中,几乎可以肯定lock的性能会更好,复杂性也会更低。谢谢Vasil,很高兴知道其他方法。@KevinGosse,你是对的。为inc.添加了一个编辑拒绝你的建议。
public class ThreadSafeStringBuilder 
{
   private readonly StringBuilder core = .....
   private readonly ReaderWriterLockSlim sync = ....

   public void Append(String str) {
      try {
         this.sync.EnterWriterLock();
         this.core.Append(str);
      }
      finally {
         this.sync.ExitWriterLock()
      }
    }

  public override ToString() {
    try {
       this.sync.EnterReadLock();
       return this.core.ToString();
    }
    finnaly {
       this.sync.ExitReadLock()
    }
  }
}