MessageQueue удалено более одного раза - PullRequest
5 голосов
/ 21 марта 2012

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

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

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

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

private MessageQueue _messageQueue;

Тогда в конструкторе класса:

this._messageQueue = MsmqHelper.InitializeQueue();

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

this._messageQueue.Send(workflowCreated);

А вот методы удаления:

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 - Ошибка? **

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

Ответы [ 3 ]

2 голосов
/ 21 марта 2012

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 в рамках всех операций с сообщениями, как показано в примере из примера документации, приведенного выше.

2 голосов
/ 21 марта 2012

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

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

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

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

1 голос
/ 21 марта 2012

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

Опять же, побуждая внешнего статического помощника, вы нарушаете шаблон 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;
    //    }
    //}
}
...