MessageQueue се изхвърля повече от веднъж

Виждал съм тази грешка в други публикации, но не и за тази точна ситуация.

Имам два класа, които правят едно и също нещо с 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 -- Грешка? **

Започвам да си мисля, че това може да е грешка. Ако коментирам 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
А, така че не задавах опашката на нула, задавах променливата (указателя към обекта) на нула. И го правех само за параметъра в метода DisposeQueue(). Това означава, че this._messageQueue все още сочи към препратката и никога не е задавано на нула. Това вярно ли е? - person Bob Horn; 21.03.2012
comment
Човек никога не задава обекти на null -- само променливи ;-) this._messageQueue все още изчислява същия обект, както преди, да. (Оценява на може да се чете като съхранява препратката към, когато говорим за типове класове.) - 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
Lasse и pst Вижте моя коментар към Lasse по-горе. Това, което Lasse предлага, работи, но този код работеше в миналото. Може би не съм имал активирани предупреждения. Трябва да направя някои тестове с това. - 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(). Така че отговорът е да преминете към be 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() преди освобождаване на управлявани/неуправлявани ресурси.

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

И вашият код може да бъде опростен по следния начин:

    // 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