MessageQueue удаляется более одного раза

Я видел эту ошибку в других сообщениях, но не в этой конкретной ситуации.

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

Объект «messageQueue» может быть удален более одного раза в методе «MsmqHelper.DisposeQueue (MessageQueue)»

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

private MessageQueue _messageQueue;

Затем в конструкторе класса:

this._messageQueue = MsmqHelper.InitializeQueue();

Не то, чтобы это действительно имело значение, но для полноты здесь используется очередь:

this._messageQueue.Send(workflowCreated);

А вот методы Dispose:

public void Dispose()
{
    Dispose(true);
    GC.SuppressFinalize(this);
}

private void Dispose(bool disposing)
{
    if (disposing == false) { return; }

    MsmqHelper.DisposeQueue(this._messageQueue);
}

А это код во вспомогательном классе, который на самом деле вызывает Dispose():

public static void DisposeQueue(MessageQueue messageQueue)
{
    if (messageQueue != null)
    {
        messageQueue.Close();
        messageQueue.Dispose();
        messageQueue = null;
    }
}

Где в этой ситуации очередь может быть удалена более одного раза?

** Редактировать **

Я подумал, что было бы неплохо добавить свои комментарии в беседе ниже, здесь. Это хорошее резюме вместе с принятым ответом:

Думаю, теперь я понял. Параметр метода messageQueue не имеет ничего общего с исходной (this._messageQueue) ссылкой на объект. Таким образом, проверка messageQueue на значение null и установка его на значение null не приносят пользы. Вызывающий объект может передать свою переменную (this._messageQueue) даже после удаления. Следовательно, возможность удаления более одного раза.

Кстати, даже установка переменной вызывающей стороны (this._messageQueue) в значение null в вызывающем методе не помогает. Проблема существует исключительно в MsmqHelper.DisposeQueue(). Таким образом, ответ заключается в том, чтобы передать ref или просто не вызывать DisposeQueue() и делать все это в вызывающем методе.

** Редактировать 2 **

Попробовав это, я получаю ту же ошибку. Я просто не понимаю.

public static void DisposeQueue(ref MessageQueue messageQueue)
{
    if (messageQueue == null) { return; }

    messageQueue.Close();
    messageQueue.Dispose();
    messageQueue = null;
}

** Изменить 3 -- Ошибка? **

Я начинаю думать, что это может быть ошибка. Если я прокомментирую messageQueue.Dispose(), ошибка исчезнет. ОДНАКО, я могу вызвать messageQueue.Close() и messageQueue.Dispose() вместе в методе calling. Иди разберись. Я думаю, что я просто буду делать те же самые вызовы из вызывающих методов или вызывать только Close() или Dispose() вместо обоих.


person Bob Horn    schedule 20.03.2012    source источник
comment
где определен метод public void Dispose()(bool disposing)?   -  person Tigran    schedule 21.03.2012
comment
Возможно, это потому, что Close и Dispose делают одно и то же?   -  person Lasse V. Karlsen    schedule 21.03.2012
comment
@Lasse Это интересная мысль. Я прокомментировал строку Close(), и она скомпилировалась. Затем я раскомментировал строку Close() и прокомментировал строку Dispose(), и она все равно скомпилировалась. Так что то, что вы сказали, может быть ответом. Я мог бы поклясться, что у меня были обе строки (Close() и Dispose()) в предыдущем коде, и это сработало. Я только что проверил, и я сделал. Теперь я не уверен, что думать.   -  person Bob Horn    schedule 21.03.2012
comment
Это должно быть предупреждение FxCop. Вас просто раздражает, что вы вызываете как Close , так и Dispose. Это излишество, просто Close в порядке.   -  person Hans Passant    schedule 21.03.2012
comment
Изменить3. Это не ошибка. Это по вашему дизайну класса.   -  person Artur Mustafin    schedule 21.03.2012
comment
... Я абстрагировал создание и удаление очереди вспомогательным классом. Таким образом, вы нарушаете шаблон одноразового использования. Подумайте об этом, тогда вы должны реализовать вызовы функций без сохранения состояния, чтобы вы не могли обрабатывать ссылку на одноразовый класс в статическом (абстрактном) вспомогательном классе или совместно использовать одни и те же экземпляры другим способом, кроме передачи ссылки на объект как параметр вызова функции   -  person Artur Mustafin    schedule 21.03.2012


Ответы (3)


Close освобождает все ресурсы объекта MessageQueue. См. документацию здесь. Ошибка, скорее всего, генерируется в CA, поскольку он видит, что путь выполнения Close также вызывает Dispose.

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

    public  void ReceiveMessage()
    {
        // Connect to the a on the local computer.
        MessageQueue myQueue = new MessageQueue(".\\myQueue");

        // Set the formatter to indicate body contains an Order.
        myQueue.Formatter = new XmlMessageFormatter(new Type[]
            {typeof(String)});

        try
        {
            // Receive and format the message. 
            Message myMessage1 = myQueue.Receive();
            Message myMessage2 = myQueue.Receive();
        }

        catch (MessageQueueException)
        {
            // Handle sources of any MessageQueueException.
        }

        // Catch other exceptions as necessary.

        finally
        {
            // Free resources.
            myQueue.Close();
        }

        return;
    }

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

*ОБНОВЛЕНИЕ* Похоже, CA обрабатывает CA2202 по-разному для полей-членов по сравнению с передачей одноразового объекта методу, даже если этот метод является частным для класса. Несмотря на это, согласно документации, вам нужно вызывать только Close() или Dispose(), но не оба. Однако я рекомендую изменить ваш дизайн, чтобы вы создавали, использовали и затем закрывали объект MessageQueue в рамках ваших операций с сообщениями, как в примере из примера документации, показанного выше.

person Jim    schedule 21.03.2012
comment
Я тоже так думал, но это не так. См. мое редактирование 3 выше. - person Bob Horn; 21.03.2012
comment
Вы можете успешно вызывать оба, но почему CA цепляется в одном случае, а в другом нет, мне не по силам. При этом вам, вероятно, следует открыть MessageQueue, использовать его и закрыть или удалить как можно скорее. Используйте close только в том случае, если ваш сценарий требует повторного получения ресурса перед сборкой мусора. В противном случае кэширование соединения позволяет снова создать/открыть последующий объект MessageQueue с небольшими накладными расходами. - person Jim; 21.03.2012
comment
Спасибо, Джим. Я принимаю ваш ответ, потому что это наиболее логично для ситуации: кажется, что CA обрабатывает CA2202 по-разному для полей-членов по сравнению с передачей одноразового объекта методу, даже если этот метод является частным для класса. - person Bob Horn; 21.03.2012

да. Это может удалить объект несколько раз:

Значение, которое вычисляет this._messageQueue, не изменяется после вызова MsmqHelper.DisposeQueue(this._messageQueue).

Только локальному параметру (с именем messageQueue) было присвоено значение null в методе DisposeQueue. Таким образом, «нулевая защита» не может правильно защитить последующие разы. (Это связано с тем, что C# по умолчанию использует Call-By-Value: см. ссылку на понять, что это означает в контексте «передачи значения ссылки на объект».)

Либо примите ref, либо назначьте this._messageQueue = null в вызывающем абоненте.

person Community    schedule 20.03.2012
comment
Ах, значит, я не устанавливал очередь в null, я устанавливал переменную (указатель на объект) в null. И я делал это только для параметра в методе DisposeQueue(). Это означает, что this._messageQueue по-прежнему указывал на ссылку и никогда не устанавливался в значение null. Это правильно? - person Bob Horn; 21.03.2012
comment
Один никогда не присваивает объектам значение null - только переменные ;-) this._messageQueue по-прежнему вычисляет тот же объект, что и раньше, да. (Вычисляет to может быть прочитано как хранит ссылку на, когда речь идет о типах классов.) - person ; 21.03.2012
comment
Обратите внимание, что компилятор жалуется на метод DisposeQueue, и этот метод будет удалять объект только более одного раза, если фактически вызывается более одного раза, но жалоба касается самого метода, а не нескольких вызовов. к этому. Я думаю, что более вероятно, что предупреждение о том, что Close и Dispose вызываются, несмотря на то, что они делают одно и то же (т.е. избавляются от объекта). - person Lasse V. Karlsen; 21.03.2012
comment
@LasseV.Karlsen Не уверен, на что будет жаловаться компилятор ... Close/Dispose должен быть идемпотентным. - person ; 21.03.2012
comment
Лассе и пст См. мой комментарий к Лассе выше. То, что предлагает Лассе, работает, но этот код работал в прошлом. Возможно, у меня не были включены предупреждения. Мне нужно сделать некоторые тесты с этим. - person Bob Horn; 21.03.2012
comment
@pst: Согласен, но обратите внимание, что я ошибался насчет компилятора, который жалуется здесь, это какая-то версия анализа кода здесь (FxCOP или анализ кода), он может увидеть что-то, чего мы здесь не видим, или просто быть чрезмерно пессимистичным. - person Lasse V. Karlsen; 21.03.2012
comment
Сказав это, я полностью согласен с вашим ответом, я просто не уверен, что это ответ на этот вопрос, но тогда я тоже не уверен, что я говорю, но вы вы совершенно правы, назначение null этой локальной переменной может быть разумным, но это, безусловно, не влияет на поле и, вероятно, не дает желаемого эффекта, которого хотел программист при вводе (т. е. предотвращения двойного удаления). - person Lasse V. Karlsen; 21.03.2012
comment
Думаю, теперь я понял. Параметр метода messageQueue не имеет ничего общего с исходной (this._messageQueue) ссылкой на объект. Таким образом, проверка messageQueue на значение null и установка его на значение null не приносят пользы. Вызывающий объект может передать свою переменную (this._messageQueue) даже после удаления. Следовательно, возможность удаления более одного раза. - person Bob Horn; 21.03.2012
comment
Кстати, даже установка переменной вызывающей стороны (this._messageQueue) в значение null в вызывающем методе не помогает. Проблема существует исключительно в MsmqHelper.DisposeQueue(). Таким образом, ответ заключается в том, чтобы пройти через ref или просто не вызывать DisposeQueue() и делать все это в вызывающем методе. - person Bob Horn; 21.03.2012
comment
Статические методы (и если да, то соответствующие статические переменные) никогда не должны использовать статические переменные IDisposable, и по этой причине не нужно бороться с шаблоном Disposable. Статические вызовы — неправильный способ управления временем жизни статического объекта. В C# предполагается, что статический объект имеет время жизни приложения, поэтому просто неправильно удалять статические члены IDisposable, потому что статический финализатор попытается сделать то же самое во время процедуры закрытия приложения, в C# вы не можете определить ваши собственные финализаторы класса, переопределить поведение можно только с помощью управляемого C++. - person Artur Mustafin; 21.03.2012
comment
@Artur Статических переменных IDisposable нет. Переменная является членом экземпляра класса экземпляра; он передается только статическому методу, потому что это тот же одноразовый код, что и в другом классе. Может быть, это все еще неправильно, но я хотел уточнить это. - person Bob Horn; 21.03.2012

Если класс MessageQueue реализует IDisposable iterface, то нет смысла явно использовать метод Dispose и метод Close(), потому что во всех таких классах метод Close() обычно является не методом iterface, а классом метод. Как правило, в методе Dispose все правильные реализации должны вызывать метод Close() перед освобождением управляемых/неуправляемых ресурсов.

Опять же, внедряя внешний статический помощник, вы нарушаете шаблон Disposable. Это неправильный способ контролировать время жизни объекта; Вам не нужно возиться с шаблоном Disposable, вы можете просто использовать его

И ваш код может быть упрощен следующим образом:

    // 1. Use static class. By the agreement, all helper classes should be static to avoid 
    // IDisposable inheritance, in example
    public static class MsmqHelper//: IDisposable
    {
        //private MessageQueue _messageQueue;

        //public MessageQueueHelper(bool workflowCreated)
        //{
        //    this._messageQueue = MsmqHelper.InitializeQueue();
        //    this._messageQueue.Send(workflowCreated);
        //}

        public static SendMessage(object workflowCreated)
        {
            // 2. If static method in static class does not takes parameters, 
            // I might be better to to implicitly call the constructor?

            // using(MessageQueue msmsq = MsmqHelper.InitializeQueue())

            using(MessageQueue msmsq = new MessageQueue())
            {
                msmq.Send(workflowCreated);
                msmq.Close(); 

                // MsmqHelper.DisposeQueue(msmq);

                // 3. You should explicitly call Close object to immediately release     
                // unmanaged resources, while managed ones will be released 
                // at next GC rounds, as soon as possible
            }
        }
        //private MessageQueue _messageQueue;

        //public void Dispose()
        //{
        //    Dispose(true);
        //    GC.SuppressFinalize(this);
        //}

        //private void Dispose(bool disposing)
        //{
    //    if (disposing == false) { return; }
    //
    //    MsmqHelper.DisposeQueue(this._messageQueue);
    //}

    //public static void DisposeQueue(MessageQueue messageQueue)
    //{
    //    if (messageQueue != null)
    //    {
    //        messageQueue.Close();
    //        messageQueue.Dispose();
    //        messageQueue = null;
    //    }
    //}
}
person Artur Mustafin    schedule 20.03.2012
comment
Артур и Джим: Спасибо за ваши ответы. Я изменю свою реализацию, чтобы использовать ваше предложение. Хотя ваши ответы не объясняют, почему я получаю ошибку CA в одном случае, а не в другом, ваши ответы все же помогают. Проблема в том, что в одном методе компилируется один и тот же код, а в другом нет. - person Bob Horn; 21.03.2012