Асинхронное выполнение трудоемкой утилизации

Представьте себе, что у вас есть класс, который что-то делает и отображает результаты в модальном окне. Класс имеет дорогостоящее удаление, т. е. ему необходимо освободить ресурсы, что может занять несколько секунд. Проблема: когда я закрываю окно, я вызываю метод удаления, чтобы освободить все объекты. Это приводит к тому, что окно зависает на несколько секунд перед закрытием. Я хочу этого избежать.

Исследования привели меня к IAsyncDisposable. Моя рабочая реализация выглядит так:

    public class MyClass : IAsyncDisposable, IDisposable
    {
        private bool disposedValue;

        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    // Disposing some costly stuff
                }
                disposedValue = true;
            }
        }

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

        public async ValueTask DisposeAsync()
        {
            await Task.Run(Dispose);
        }
    }

Вызов его через:

myClassInstance.DisposeAsync().ConfigureAwait(false);

Вопрос: Это допустимо, особенно реализация метода DisposeAsync? Может ли здесь что-то пойти не так? Это работает хорошо, но я почему-то не уверен.

Я провел небольшое исследование по этому поводу, но не нашел никого, кто делал бы это таким образом. Более того, Visual Studio говорит мне, что мне следует что-то сделать с ValueTask, который я получаю при вызове метода DisposeAsync. Есть какие-нибудь комментарии по этому поводу?

Почему что-то зависает, если вы не ждете своего освобождения?

GSerg 21.08.2024 12:36

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

Philipp 21.08.2024 12:39

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

Zazaeil 21.08.2024 12:41

«Visual Studio говорит мне…» — о чем это вам говорит?

Sinatr 21.08.2024 12:41

асинхронность и параллелизм — связанные, но принципиально разные вещи; вы пытаетесь решить что-то с помощью async, но это не проблема - вы можете просто использовать это как фоновую операцию, не добавляя и не удаляя ничего асинхронного - Task.Run или ThreadPool.QueueUserWorkItem будут работать без необходимости async/IAsyncDisposable.

Marc Gravell 21.08.2024 12:42

Значит, перед await нет myClassInstance.DisposeAsync().ConfigureAwait(false);?

Theodor Zoulias 21.08.2024 12:43

@Sinatr: Подсказка CA2012, поэтому перед вызовом DisposeAsync также нет ожидания.

Philipp 21.08.2024 12:45

@MarcGravell, возможно, это сделано таким образом, чтобы он мог контролировать, когда удалять синхронно, а когда удалять в фоновом режиме?

Mark Cilia Vincenti 21.08.2024 12:45

как гнида: похоже, ваш план состоит в том, чтобы не await obj.DisposeAsync() - но: для 100% корректной работы необходимо await (или хотя бы соблюдать) каждый ValueTask/ValueTask<T> ровно один раз (не ноль раз, не два раза и т. д.; это из-за того, как работает IValueTaskSource/IValueTaskSource<T> (это нишевый API, и большинство важных задач не будут его использовать, но дело в том, что это деталь реализации, о которой вызывающая сторона не знает)

Marc Gravell 21.08.2024 12:47

@MarkCiliaVincenti для этого тебе не нужен DisposeAsync

Ralf 21.08.2024 12:47

@MarcGravell В этом есть смысл, я просто подумал, что следую интерфейсу IAsyncDisposable, поскольку MS предоставляет это.

Philipp 21.08.2024 12:48

@MarkCiliaVincenti «удалить в фоновом режиме» — это не то же самое, что «удалить асинхронно» - этот код в значительной степени объединяет эти два понятия, что бесполезно; использование Task.Run для запуска чего-либо не делает его асинхронным, что здесь имеет значение

Marc Gravell 21.08.2024 12:49

@Филипп, но ты не подписан IAsyncDisposable; для этого вам необходимо await удалить приложение, что означает, что ваше приложение все равно будет заблокировано; async речь идет не о том, чтобы избежать задержек, которые могут (например) заблокировать пользовательский интерфейс; речь идет о том, чтобы позволить процессору выполнять полезную несвязанную работу, пока он заблокирован, и здесь проблема не в этом. Если вы хотите сделать что-то в фоновом режиме: просто сделайте это в фоновом режиме.

Marc Gravell 21.08.2024 12:50

@MarcGravell Да, я знаю, что с технической точки зрения это не одно и то же, но, судя по определению из английского словаря, это почти то же самое, поэтому и возникает такая путаница. Однако ради того, чтобы поступать правильно, ему следует пересмотреть то, как он это делает.

Mark Cilia Vincenti 21.08.2024 12:52

@MarcGravell Это полезный совет, спасибо за разъяснения. Я уверен, что где-то я уже писал подобное заявление, но почему-то оно не задерживается у меня в памяти. Наверное, напишу это на стикере и положу на экран....

Philipp 21.08.2024 12:55

«Обычные» английские словари редко бывают полезны при обсуждении того, как термины используются в каком-либо конкретном специализированном техническом контексте ;p (и я не ограничиваюсь программированием)

Marc Gravell 21.08.2024 12:55

@MarcGravell Я знаю, верно? К сожалению, я думаю, что в целом выбор формулировки асинхронного кода оставляет эту двусмысленность, из-за которой люди слишком легко могут неправильно понять, как он работает.

Mark Cilia Vincenti 21.08.2024 12:57
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
18
58
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Если вы хотите удалить что-то в фоновом режиме (и это удаление по своей сути синхронно), просто удалите это в фоновом режиме; например вместо:

obj.Dispose();

учитывать:

ThreadPool.QueueUserWorkItem<IDisposable>(static s => s.Dispose(), obj, false);

В конечном счете, асинхронность и параллелизм — это связанные, но разные концепции; в данном случае нам нужен параллелизм, а асинхронность не связана с ней — нет необходимости (или цели) добавлять или удалять ее.

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