Как правильно заблокировать StringBuilder, чтобы он не выдавал ArgumentOutOfRangeException

Я прочитал несколько сообщений, как по переполнению стека, так и по всему миру, что StringBuilder не является потокобезопасным, и при доступе (чтение / запись к нему) из нескольких потоков должен быть заблокирован: здесь, здесь, здесь. Мне неясно, как заблокировать мои экземпляры Stringbuilder, в частности, сколько блокировок нужно добавить и где.

У меня есть следующий код (фрагмент кода):

    class MemoryTracker
    {
        private Process extProc    { get; set; }
        private StringBuilder sb   { get; set; }

        internal int trackByIP(string ip)
        {
             ...
             ...
             sb = new StringBuilder();
             extProc.OutputDataReceived  += new DataReceivedEventHandler((s, e)  => sb.Append(e.Data + "\n"));
             extProc.ErrorDataReceived   += new DataReceivedEventHandler((s, e)  => sb.Append(e.Data + "\n"));

             extProc.Start();
             extProc.PriorityClass       = ProcessPriorityClass.RealTime;
             ...
             ...
        }

        string getDataWStartEndPttrn(StringBuilder data, string strPttr, string endPttr, string extendedTerminator)
        {
             string s = data.ToString(); // <-- THROWS ArgumentOutOfRangeException 

             int si = getStartIdx(s, strPttr, patternDiff(strPttr, endPttr));
             int se = getEndIdx(s, endPttr, patternDiff(endPttr, strPttr));

             if (se >= 0 && si >= 0)
             {
                 string s1 = s.Substring(si, se - si);

                 string sTMP = s.Substring(se);
                 string s2 = s.Substring(se, sTMP.IndexOf(extendedTerminator));

                 return s1 + s2;
             }

             return "";
        }

Установив замок, я все равно вижу ту же ошибку.

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 
        }
 ...
 ...

ВОПРОС: Как правильно подумать, где разместить замок / замки? Там нет места, где я явно создаю потоки, поэтому я предположил, что это будет безопасное использование потока StringBuilder.toString();

1 ответ

Альтернативное решение

Создайте потокобезопасную оболочку вокруг StringBuilder и используйте ее вместо текущей. Это будет инкапсулировать логику синхронизации в классе оболочки. Я думаю, что это немного лучше, вместо того, чтобы блокировать методы, которые используют класс StringBuilder.

Используйте ReaderWriteLockSlim. Это позволяет нескольким потокам получать доступ к блоку кода, когда блокировка считывателя получена, и единственный поток, к которому можно получить доступ, когда блокировка записи получена. Просто если несколько потоков пытается использовать .ToString() метод это будет хорошо, и если в то же время некоторые другие потоки пытается использовать .Append() Конкретно, этот поток будет ждать всех других потоков с чтением (.ToString()) заканчивать.

Вот очень простой смысл, это должно быть хорошей отправной точкой.

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 на блокировку статистики и блокировку для обоих .Append() а также .ToString() должен блокироваться на том же объекте.

Другие вопросы по тегам