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