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

Почему бы попробовать/наконец, а не использовать "использование", помочь избежать состояния гонки?

Этот вопрос относится к комментарию в другом сообщении здесь: Отмена запроса платформы Entity

Я буду воспроизводить пример кода оттуда для ясности:

    var thread = new Thread((param) =>
    {
        var currentString = param as string;

        if (currentString == null)
        {
            // TODO OMG exception
            throw new Exception();
        }

        AdventureWorks2008R2Entities entities = null;
        try // Don't use using because it can cause race condition
        {
            entities = new AdventureWorks2008R2Entities();

            ObjectQuery<Person> query = entities.People
                .Include("Password")
                .Include("PersonPhone")
                .Include("EmailAddress")
                .Include("BusinessEntity")
                .Include("BusinessEntityContact");
            // Improves performance of readonly query where
            // objects do not have to be tracked by context
            // Edit: But it doesn't work for this query because of includes
            // query.MergeOption = MergeOption.NoTracking;

            foreach (var record in query 
                .Where(p => p.LastName.StartsWith(currentString)))
            {
                // TODO fill some buffer and invoke UI update
            }
        }
        finally
        {
            if (entities != null)
            {
                entities.Dispose();
            }
        }
    });

thread.Start("P");
// Just for test
Thread.Sleep(500);
thread.Abort();

Я не могу понять комментарий, который говорит

Не используйте использование, потому что оно может вызывать состояние гонки

entities является локальной переменной и не будет использоваться, если код будет повторно введен в другой поток, и в пределах одного потока он будет казаться совершенно безопасным (и действительно эквивалентным заданному коду), чтобы назначить его внутри "использовать" выражение обычным способом, а не делать вещи вручную с помощью try/finally. Может ли кто-нибудь просветить меня?

4b9b3361

Ответ 1

Да, в заявлении использования есть возможная гонка. Компилятор С# преобразует

using (var obj = new Foo()) {
    // statements
}

в

var obj = new Foo();
try {
   // statements
}
finally {
   if (obj != null) obj.Dispose();
}

Гонка происходит, когда поток прерывается прямо между оператором присваивания obj и блоком try. Чрезвычайно малые коэффициенты, но не ноль. Когда это произойдет, объект не будет удален. Обратите внимание, как он переписал этот код, перемещая назначение внутри блока try, чтобы эта гонка не могла произойти. Ничто на самом деле не является принципиально неправильным, когда происходит гонка, удаление объектов не является обязательным требованием.

Чтобы выбрать между отменой прерывания потока и более эффективным использованием письменных инструкций, вы должны сначала отказаться от привычки использовать Thread.Abort(). Я не могу рекомендовать на самом деле делать это, у оператора использования есть дополнительные меры безопасности, чтобы избежать несчастных случаев, он гарантирует, что исходный объект будет удален, даже если объект будет переопределен внутри оператора using. Добавление предложений catch менее подвержено несчастным случаям. Оператор using существует для уменьшения вероятности ошибок, всегда используйте его.


Немного об этой проблеме, ответ популярен, есть еще один общий оператор С#, который страдает от той же гонки. Это выглядит так:

lock (obj) {
    // statements
}

Переведено на:

Monitor.Enter(obj);
// <=== Eeeek!
try {
    // statements
}
finally {
    Monitor.Exit(obj);
}

Точный такой же сценарий, прерывание потока может ударить после вызова Enter() и перед входом в блок try. Это предотвращает выполнение вызова Exit(). Это более неприятно, чем вызов Dispose(), который не сделан, конечно, это почти наверняка вызовет тупик. Проблема специфична для джиттера x64, подробности о грубых деталях хорошо описаны в этом сообщении Joe Duffy.

Очень сложно надежно исправить это, перемещение вызова Enter() внутри блока try не может решить проблему. Вы не можете быть уверены, что был вызван вызов Enter, поэтому вы не можете надежно вызвать метод Exit(), не вызывая при этом исключения. Метод Monitor.ReliableEnter(), о котором говорил Даффи, в конце концов произошел. Версия .NET 4 для Monitor получила перегрузку TryEnter(), которая принимает аргумент ref bool lockTaken. Теперь вы знаете, что нормально вызывать Exit().

Ну, страшный материал, который идет БУМП в ночь, когда ты не смотришь. Написание кода, который безопасно прерывается, затруднен. Вам было бы разумно никогда не предполагать, что код, который вы не писали, все это позаботился. Тестирование такого кода чрезвычайно сложно, так как гонка настолько редка. Вы никогда не можете быть уверены.

Ответ 2

Очень странно, причина using - это только синтаксический сахар для блока try - finally.

Из MSDN:

Вы можете добиться того же результата, поставив объект внутри попытки блок, а затем вызов Dispose в блоке finally; на самом деле это как оператор using транслируется компилятором.

Ответ 3

В зависимости от того, используете ли вы using или явно try/finally, вы можете иметь немного другой код, используя код примера, который у вас будет

    AdventureWorks2008R2Entities entities = null;
    try // Don't use using because it can cause race condition
    {
        entities = new AdventureWorks2008R2Entities();
        ...
    } finally {
    } 

подставляя это с помощью инструкции using, она может выглядеть как

   using(var entities = new AdventureWorks2008R2Entities()){
      ...
   }

который согласно § 8.13 спецификации будет расширен до

    AdventureWorks2008R2Entities entities = new AdventureWorks2008R2Entities();
    try
    {
        ...
    } finally {
    } 

Поэтому единственное реальное различие заключается в том, что присваивание не находится в блоке try/finally, но это не имеет последствий, по которым могут возникать условия гонки (кроме потока прерывания между назначением и блоком try, как отмечает Ханс)

Ответ 4

Этот комментарий не имеет никакого смысла, поскольку оператор using будет переводиться в блок try/finally компилятором. Поскольку "сущности" не будут использоваться вне области видимости, проще использовать используемую запись, так как это автоматически удалит ресурсы.

Подробнее об этом вы можете узнать в MSDN: с использованием Statement (С# Reference).