Как удалить объекты, имеющие асинхронные методы?

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

    private void Preload(SlideHandler slide)
    {
        using(PreloadClient client = new PreloadClient())
        {                 
             client.PreloadCompleted += client_PreloadCompleted;
             client.Preload(slide);
        }
        // Here client is disposed immediately
    }
    private void client_PreloadCompleted(object sender, SlidePreloadCompletedEventArgs e)
    {
     // this is method is called after a while, 
     // but errors are thrown when trying to access object state (fields, properties)
    }

Итак, любые идеи или работа вокруг?

Ответ 1

  • Вы не должны использовать конструкцию using, а скорее удаляете свои объекты, когда они больше не нужны:

    // keep a list of strong references to avoid garbage collection,
    // and dispose them all in case we're disposing the encapsulating object
    private readonly List<PreloadClient> _activeClients = new List<PreloadClient>();
    private void Preload(SlideHandler slide)
    {
        PreloadClient client = new PreloadClient();
        _activeClients.Add(client);
        client.PreloadCompleted += client_PreloadCompleted;
        client.Preload(slide);
    }
    
    private void client_PreloadCompleted(object sender,
         SlidePreloadCompletedEventArgs e)
    {
        PreloadClient client = sender as PreloadClient;
    
        // do stuff
    
        client.PreloadCompleted -= client_PreloadCompleted;
        client.Dispose();
        _activeClients.Remove(client);
    }
    
  • в этом случае вы должны распоряжаться всеми клиентами при утилизации основного класса:

    protected override Dispose(bool disposing)
    {
        foreach (PreloadClient client in _activeClients)
        { 
            client.PreloadCompleted -= client_PreloadCompleted;
            client.Dispose();
        }
        _activeClients.Clear();
        base.Dispose(disposing);
    }
    
  • Обратите внимание, что эта реализация не является потокобезопасной

    • Доступ к списку _activeClients должен be сделать потокобезопасным, как ваш метод PreloadCompleted вызывается из другого потока
    • Ваш объект может быть удален до того, как клиент выполнит это событие. В этом случае "делать вещи" ничего не должно делать, так что это еще одна вещь, о которой вам следует позаботиться.
    • Это может быть хорошей идеей использовать блок try/finally внутри вашего обработчика событий, чтобы убедиться, что объект удален во всех случаях.

Ответ 2

Почему бы не удалить клиента в обратном вызове?

Ответ 3

У меня есть несколько идей:

  • измените вашу архитектуру.
  • распоряжаться в обработчике
  • использовать EventWaitHandle

Ответ 4

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

Что-то вроде

class ContainingClass : IDisposable
{
    private PreloadClient m_Client;

    private void Preload(SlideHandler slide)
    {
         m_Client = new PreloadClient())

         m_Client.PreloadCompleted += client_PreloadCompleted;
         m_Client.Preload(slide);

    }
    private void client_PreloadCompleted(object sender, SlidePreloadCompletedEventArgs e)
    {
    }

    public void Dispose()
    {
        if (m_Client != null)
            m_Client.Dispose();
    }
}

Ответ 5

Ну, удаление объекта используется для уничтожения ресурсов, которые вы не хотите удерживать до тех пор, пока GC (в конце концов) не придет и не соберет ваш объект. Ваш метод уничтожения убивает все, что вам нужно, в client_PreloadCompleted?

У вас может быть объект самостоятельно, когда все ожидаемые обратные вызовы произойдут: сохраните "счетчик ссылок" для каждого обратного вызова, который вы ожидаете, и уменьшите его количество при каждом обратном вызове - проверьте значение null в конце обработчика обратного вызова и удалите if поэтому.

Другое обходное решение: не беспокойтесь о IDisposable. GC соберет ваш объект. Вероятно, вы не хотите, чтобы обработчик обратного вызова (который не мог быть запущен) имел критическое состояние. Он (обратный вызов) должен просто открывать любые ресурсы, которые ему нужны, когда он вызывается, и затем закрывать их.

Ответ 6

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

Ответ 7

Почему бы не утилизировать метод client_PreloadCompleted? Подобно тому, что предложил thecoop, только с вызовом Dispose внутри указанного метода после того, как вы получили доступ ко всем необходимым данным изнутри объекта клиента.

Редактировать: Я думаю, что это то, что предложил orialmog.