Представьте себе, что у вас есть класс, который что-то делает и отображает результаты в модальном окне. Класс имеет дорогостоящее удаление, т. е. ему необходимо освободить ресурсы, что может занять несколько секунд. Проблема: когда я закрываю окно, я вызываю метод удаления, чтобы освободить все объекты. Это приводит к тому, что окно зависает на несколько секунд перед закрытием. Я хочу этого избежать.
Исследования привели меня к 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. Есть какие-нибудь комментарии по этому поводу?
Ну, афаик, Dispose - это всего лишь метод. Когда я вызываю его, это синхронный вызов. Если что-то в Dispose занимает много времени, оно эффективно блокирует вызывающий поток. Как это называется в потоке представления (окна), оно замораживает окно.
Это может сработать, если вас не волнуют потерянные исключения.
«Visual Studio говорит мне…» — о чем это вам говорит?
асинхронность и параллелизм — связанные, но принципиально разные вещи; вы пытаетесь решить что-то с помощью async
, но это не проблема - вы можете просто использовать это как фоновую операцию, не добавляя и не удаляя ничего асинхронного - Task.Run
или ThreadPool.QueueUserWorkItem
будут работать без необходимости async
/IAsyncDisposable
.
Значит, перед await
нет myClassInstance.DisposeAsync().ConfigureAwait(false);
?
@Sinatr: Подсказка CA2012, поэтому перед вызовом DisposeAsync также нет ожидания.
@MarcGravell, возможно, это сделано таким образом, чтобы он мог контролировать, когда удалять синхронно, а когда удалять в фоновом режиме?
как гнида: похоже, ваш план состоит в том, чтобы не await obj.DisposeAsync()
- но: для 100% корректной работы необходимо await
(или хотя бы соблюдать) каждый ValueTask
/ValueTask<T>
ровно один раз (не ноль раз, не два раза и т. д.; это из-за того, как работает IValueTaskSource
/IValueTaskSource<T>
(это нишевый API, и большинство важных задач не будут его использовать, но дело в том, что это деталь реализации, о которой вызывающая сторона не знает)
@MarkCiliaVincenti для этого тебе не нужен DisposeAsync
@MarcGravell В этом есть смысл, я просто подумал, что следую интерфейсу IAsyncDisposable, поскольку MS предоставляет это.
@MarkCiliaVincenti «удалить в фоновом режиме» — это не то же самое, что «удалить асинхронно» - этот код в значительной степени объединяет эти два понятия, что бесполезно; использование Task.Run
для запуска чего-либо не делает его асинхронным, что здесь имеет значение
@Филипп, но ты не подписан IAsyncDisposable
; для этого вам необходимо await
удалить приложение, что означает, что ваше приложение все равно будет заблокировано; async
речь идет не о том, чтобы избежать задержек, которые могут (например) заблокировать пользовательский интерфейс; речь идет о том, чтобы позволить процессору выполнять полезную несвязанную работу, пока он заблокирован, и здесь проблема не в этом. Если вы хотите сделать что-то в фоновом режиме: просто сделайте это в фоновом режиме.
@MarcGravell Да, я знаю, что с технической точки зрения это не одно и то же, но, судя по определению из английского словаря, это почти то же самое, поэтому и возникает такая путаница. Однако ради того, чтобы поступать правильно, ему следует пересмотреть то, как он это делает.
@MarcGravell Это полезный совет, спасибо за разъяснения. Я уверен, что где-то я уже писал подобное заявление, но почему-то оно не задерживается у меня в памяти. Наверное, напишу это на стикере и положу на экран....
«Обычные» английские словари редко бывают полезны при обсуждении того, как термины используются в каком-либо конкретном специализированном техническом контексте ;p (и я не ограничиваюсь программированием)
@MarcGravell Я знаю, верно? К сожалению, я думаю, что в целом выбор формулировки асинхронного кода оставляет эту двусмысленность, из-за которой люди слишком легко могут неправильно понять, как он работает.
Если вы хотите удалить что-то в фоновом режиме (и это удаление по своей сути синхронно), просто удалите это в фоновом режиме; например вместо:
obj.Dispose();
учитывать:
ThreadPool.QueueUserWorkItem<IDisposable>(static s => s.Dispose(), obj, false);
В конечном счете, асинхронность и параллелизм — это связанные, но разные концепции; в данном случае нам нужен параллелизм, а асинхронность не связана с ней — нет необходимости (или цели) добавлять или удалять ее.
Почему что-то зависает, если вы не ждете своего освобождения?