Вопрос

Допустим, у меня есть класс, реализующий IDодноразовый интерфейс.Что-то вроде этого:

http://www.flickr.com/photos/garthof/3149605015/

Мои занятия использует некоторые неуправляемые ресурсы, поэтому Утилизировать() метод из IDодноразовый освобождает эти ресурсы. Мои занятия следует использовать следующим образом:

using ( MyClass myClass = new MyClass() ) {
    myClass.DoSomething();
}

Теперь я хочу реализовать метод, который вызывает Сделай что-нибудь() асинхронно.Я добавляю новый метод в Мои занятия:

http://www.flickr.com/photos/garthof/3149605005/

Теперь со стороны клиента: Мои занятия следует использовать следующим образом:

using ( MyClass myClass = new MyClass() ) {
    myClass.AsyncDoSomething();
}

Однако, если я больше ничего не сделаю, это может потерпеть неудачу, так как объект мои занятия может быть утилизировано раньше Сделай что-нибудь() вызывается (и выдает неожиданный Объектдиспоуздисцептион).Итак, вызов в Утилизировать() метод (неявный или явный) должен быть отложен до тех пор, пока не будет выполнен асинхронный вызов Сделай что-нибудь() готово.

Я думаю, что код в Утилизировать() метод должен быть выполнен асинхронно и только после того, как все асинхронные вызовы будут разрешены.Я хотел бы знать, какой способ лучше всего это сделать.

Спасибо.

ПРИМЕЧАНИЕ:Для простоты я не стал вдаваться в подробности реализации метода Dispose().В реальной жизни я обычно следую Шаблон утилизации.


ОБНОВЛЯТЬ: Большое спасибо за ваши ответы.Я ценю ваши усилия.Как чакрит прокомментировал, Я нуждаюсь в этом можно выполнить несколько вызовов асинхронного DoSomething.В идеале что-то вроде этого должно работать нормально:

using ( MyClass myClass = new MyClass() ) {

    myClass.AsyncDoSomething();
    myClass.AsyncDoSomething();

}

Изучу счетный семафор, кажется то, что ищу.Это также может быть проблема с дизайном.Если мне будет удобно, я поделюсь с вами некоторыми деталями реального дела и того, что Мои занятия действительно так.

Это было полезно?

Решение 6

Итак, моя идея состоит в том, чтобы сохранить, сколько АсинхронныйДоНечто() ожидают завершения и удаляются только тогда, когда этот счетчик достигнет нуля.Мой первоначальный подход:

public class MyClass : IDisposable {

    private delegate void AsyncDoSomethingCaller();
    private delegate void AsyncDoDisposeCaller();

    private int pendingTasks = 0;

    public DoSomething() {
        // Do whatever.
    }

    public AsyncDoSomething() {
        pendingTasks++;
        AsyncDoSomethingCaller caller = new AsyncDoSomethingCaller();
        caller.BeginInvoke( new AsyncCallback( EndDoSomethingCallback ), caller);
    }

    public Dispose() {
        AsyncDoDisposeCaller caller = new AsyncDoDisposeCaller();
        caller.BeginInvoke( new AsyncCallback( EndDoDisposeCallback ), caller);
    }

    private DoDispose() {
        WaitForPendingTasks();

        // Finally, dispose whatever managed and unmanaged resources.
    }

    private void WaitForPendingTasks() {
        while ( true ) {
            // Check if there is a pending task.
            if ( pendingTasks == 0 ) {
                return;
            }

            // Allow other threads to execute.
            Thread.Sleep( 0 );
        }
    }

    private void EndDoSomethingCallback( IAsyncResult ar ) {
        AsyncDoSomethingCaller caller = (AsyncDoSomethingCaller) ar.AsyncState;
        caller.EndInvoke( ar );
        pendingTasks--;
    }

    private void EndDoDisposeCallback( IAsyncResult ar ) {
        AsyncDoDisposeCaller caller = (AsyncDoDisposeCaller) ar.AsyncState;
        caller.EndInvoke( ar );
    }
}

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

public class MyClass : IDisposable {

    private delegate void AsyncDoSomethingCaller();
    private delegate void AsyncDoDisposeCaller();

    private int pendingTasks = 0;
    private readonly object lockObj = new object();

    public DoSomething() {
        // Do whatever.
    }

    public AsyncDoSomething() {
        lock ( lockObj ) {
            pendingTasks++;
            AsyncDoSomethingCaller caller = new AsyncDoSomethingCaller();
            caller.BeginInvoke( new AsyncCallback( EndDoSomethingCallback ), caller);
        }
    }

    public Dispose() {
        AsyncDoDisposeCaller caller = new AsyncDoDisposeCaller();
        caller.BeginInvoke( new AsyncCallback( EndDoDisposeCallback ), caller);
    }

    private DoDispose() {
        WaitForPendingTasks();

        // Finally, dispose whatever managed and unmanaged resources.
    }

    private void WaitForPendingTasks() {
        while ( true ) {
            // Check if there is a pending task.
            lock ( lockObj ) {
                if ( pendingTasks == 0 ) {
                    return;
                }
            }

            // Allow other threads to execute.
            Thread.Sleep( 0 );
        }
    }

    private void EndDoSomethingCallback( IAsyncResult ar ) {
        lock ( lockObj ) {
            AsyncDoSomethingCaller caller = (AsyncDoSomethingCaller) ar.AsyncState;
            caller.EndInvoke( ar );
            pendingTasks--;
        }
    }

    private void EndDoDisposeCallback( IAsyncResult ar ) {
        AsyncDoDisposeCaller caller = (AsyncDoDisposeCaller) ar.AsyncState;
        caller.EndInvoke( ar );
    }
}

Я вижу проблему в этом подходе.Поскольку высвобождение ресурсов происходит асинхронно, может работать что-то вроде этого:

MyClass myClass;

using ( myClass = new MyClass() ) {
    myClass.AsyncDoSomething();
}

myClass.DoSomething();

Когда ожидаемым поведением должен быть запуск Объектдиспоуздисцептион когда Сделай что-нибудь() называется вне с использованием пункт.Но я не считаю это настолько плохим, чтобы переосмыслить это решение.

Другие советы

Похоже, вы используете асинхронный шаблон на основе событий (дополнительную информацию об асинхронных шаблонах .NET см. здесь.), поэтому обычно у вас есть событие в классе, которое срабатывает после завершения асинхронной операции с именем DoSomethingCompleted (Обратите внимание, что AsyncDoSomething действительно следует позвонить DoSomethingAsync правильно следовать схеме).Открыв это событие, вы можете написать:

var myClass = new MyClass();
myClass.DoSomethingCompleted += (sender, e) => myClass.Dispose();
myClass.DoSomethingAsync();

Другой альтернативой является использование IAsyncResult шаблон, в котором вы можете передать делегат, вызывающий метод удаления, в AsyncCallback параметр (более подробная информация об этом шаблоне также находится на странице выше).В этом случае у вас будет BeginDoSomething и EndDoSomething методы вместо DoSomethingAsync, и назвал бы это как-то так...

var myClass = new MyClass();
myClass.BeginDoSomething(
    asyncResult => {
                       using (myClass)
                       {
                           myClass.EndDoSomething(asyncResult);
                       }
                   },
    null);        

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

Асинхронные методы обычно имеют обратный вызов, позволяющий выполнить некоторые действия после завершения.Если это ваш случай, это будет примерно так:

// The async method taks an on-completed callback delegate
myClass.AsyncDoSomething(delegate { myClass.Dispose(); });

Другой способ обойти это — асинхронная оболочка:

ThreadPool.QueueUserWorkItem(delegate
{
    using(myClass)
    {
        // The class doesn't know about async operations, a helper method does that
        myClass.DoSomething();
    }
});

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

Вы можете добавить механизм обратного вызова и передать функцию очистки в качестве обратного вызова.

var x = new MyClass();

Action cleanup = () => x.Dispose();

x.DoSomethingAsync(/*and then*/cleanup);

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

Одним из способов было бы реализовать простой счетный семафор с Класс семафора для подсчета количества запущенных асинхронных заданий.

Добавьте счетчик в MyClass и при каждом вызове AsyncWhatever увеличивайте счетчик, а при выходе уменьшайте его.Когда семафор равен 0, класс готов к удалению.

var x = new MyClass();

x.DoSomethingAsync();
x.DoSomethingAsync2();

while (x.RunningJobsCount > 0)
    Thread.CurrentThread.Sleep(500);

x.Dispose();

Но я сомневаюсь, что это будет идеальный путь.Чувствую проблему с дизайном.Может быть, переосмысление дизайна MyClass поможет избежать этого?

Не могли бы вы поделиться некоторой реализацией MyClass?Что он должен делать?

Я считаю прискорбным, что Microsoft не потребовала в рамках IDisposable контракт, согласно которому реализации должны позволять Dispose вызываться из любого контекста потока, поскольку не существует разумного способа создания объекта обеспечить продолжение существования контекста потока, в котором он был создан.Можно спроектировать код так, чтобы поток, создающий объект, каким-то образом следил за тем, чтобы объект устарел, и мог Dispose так, как это удобно, и чтобы, когда нить больше не понадобится для чего-либо еще, она оставалась бы там, пока все подходящие объекты не будут Disposed, но я не думаю, что существует стандартный механизм, который не требовал бы особого поведения со стороны потока, создающего Dispose.

Лучше всего, вероятно, создать все интересующие объекты в одном общем потоке (возможно, потоке пользовательского интерфейса), попытаться гарантировать, что поток будет оставаться в течение всего времени существования интересующих объектов, и использовать что-то вроде Control.BeginInvoke потребовать утилизации объектов.При условии, что ни создание объекта, ни очистка не будут блокироваться в течение какого-либо периода времени, это может быть хорошим подходом, но если какая-либо операция может блокировать, может потребоваться другой подход [возможно, откройте скрытую фиктивную форму с ее собственным потоком, чтобы можно было использовать Control.BeginInvoke там].

Альтернативно, если у вас есть контроль над IDisposable реализации, проектируйте их так, чтобы их можно было безопасно запускать асинхронно.Во многих случаях это «просто сработает» при условии, что никто не пытается использовать предмет, когда он будет удален, но это вряд ли является данностью.В частности, со многими видами IDisposable, существует реальная опасность того, что несколько экземпляров объекта могут одновременно манипулировать общим внешним ресурсом [например.объект может содержать List<> созданных экземпляров, добавлять экземпляры в этот список при их создании и удалять экземпляры на Dispose;если операции списка не синхронизированы, асинхронный Dispose может повредить список, даже если объект, подлежащий удалению иначе не используется.

Кстати, полезным шаблоном является то, что объекты позволяют асинхронное удаление во время их использования, с ожиданием, что такое удаление приведет к тому, что любые выполняемые операции вызовут исключение при первой удобной возможности.Такие вещи, как сокеты, работают таким образом.Операция чтения может быть невозможна досрочно завершиться, не оставив сокет в бесполезном состоянии, но если сокет все равно никогда не будет использоваться, нет смысла продолжать чтение данных, если другой поток определил, что это оно должно сдаться.ИМХО, все так IDisposable объекты должны стремиться вести себя хорошо, но я не знаю ни одного документа, подтверждающего такую ​​общую закономерность.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top