Подтвердить что ты не робот

Является .NET StringBuilder потокобезопасным

В разделе "Безопасность потока" в документации MSDN для StringBuilder указано, что:

... любые члены экземпляра не гарантируют безопасность потоков...

но это утверждение похоже, что оно было скопировано и вставлено почти для каждого класса в Framework:

http://msdn.microsoft.com/en-us/library/system.text.stringbuilder.aspx

Тем не менее, в этих блогах Gavin Pugh упоминаются потокобезопасные поведения StringBuilder:

http://www.gavpugh.com/2010/03/23/xnac-stringbuilder-to-string-with-no-garbage/

http://www.gavpugh.com/2010/04/01/xnac-avoiding-garbage-when-working-with-stringbuilder/

Кроме того, источник StringBuilder, обнаруженный Reflector, и сопровождающие комментарии  в источнике SSCLI, также предлагают множество соображений по внедрению для обеспечения безопасности потоков:

http://labs.developerfusion.co.uk/SourceViewer/browse.aspx?assembly=SSCLI&namespace=System.Text&type=StringBuilder

Есть ли у кого-нибудь более глубокое понимание того, может ли экземпляр StringBuilder безопасно делиться между несколькими параллельными потоками?

4b9b3361

Ответ 1

Абсолютно нет; здесь простой пример снят с 4,0 через отражатель:

[SecuritySafeCritical]
public StringBuilder Append(char value)
{
    if (this.m_ChunkLength < this.m_ChunkChars.Length)
    {
        this.m_ChunkChars[this.m_ChunkLength++] = value;
    }
    else
    {
        this.Append(value, 1);
    }
    return this;
}

Атрибут обрабатывает только вызывающие, а не поточную безопасность; это абсолютно не потокобезопасно.

Обновление: глядя на источник, который он ссылается, это явно не текущая кодовая база .NET 4.0 (сравнение нескольких методов). Возможно, он говорит о конкретной версии .NET или, возможно, о XNA, но в целом это не. В 4.0 StringBuilder не есть a m_currentThread поле, которое использует исходный материал Gavin; есть подсказка (неиспользуемая константа ThreadIDField), которую он использовал, но... больше не работает.


Если вы хотите direct disproof - запустите это на 4.0; он, скорее всего, даст неправильную длину (я видел несколько в области 4k, несколько в области 2k - это должно быть ровно 5000), но некоторые другие методы Append (Append(char) например) имеют тенденцию больше вероятно, будет генерировать исключения, в зависимости от времени:

var gate = new ManualResetEvent(false);
var allDone = new AutoResetEvent(false);
int counter = 0;
var sb = new StringBuilder();
ThreadStart work = delegate
{
    // open gate when all 5 threads are running
    if (Interlocked.Increment(ref counter) == 5) gate.Set();
    else gate.WaitOne();

    for (int i = 0; i < 1000; i++) sb.Append("a");

    if (Interlocked.Decrement(ref counter) == 0) allDone.Set();
};
for(int i = 0 ; i < 5 ; i++)
{
    new Thread(work).Start();
}
allDone.WaitOne();
Console.WriteLine(sb.Length);

Ответ 2

Весь смысл документации - предоставить вам гарантии. В этом случае члены экземпляра ничего не гарантируют потокобезопасность, и вы должны рассматривать его как таковые, поэтому полагаться на внешние методы синхронизации.

То, что некоторые вещи могут быть потокобезопасными, - это деталь реализации, которая может и, возможно, может изменяться от одной версии фреймворка к следующей или от одной реализации к следующей (на самом деле в таких версиях существует множество таких деталей; У Lippert есть несколько сообщений, в которых подробно описаны некоторые из них). Не полагайтесь на это.

(Другими словами: не записывайте код в реализацию, пишите его по интерфейсу и контракту, которые в этом случае являются метаданными класса и его документации.)

Ответ 3

Из документации MSDN:

Все публичные статические (Shared in Visual Basic) члены этого типа безопасный поток. Любые члены экземпляра не гарантированно являются потоками сейф.