Я пытался создать модель представления, в которой некоторые свойства загружаются из фонового потока.
Для этой задачи я нашел старую статью Стивена Клири в MSDN на NotifyTaskCompletion<T>. Листинг кода «Рисунок 4» в разделе Лучший подход выглядит следующим образом:
public sealed class NotifyTaskCompletion<TResult> : INotifyPropertyChanged
{
public NotifyTaskCompletion(Task<TResult> task)
{
Task = task;
if (!task.IsCompleted) {
var _ = WatchTaskAsync(task);
}
}
private async Task WatchTaskAsync(Task task) {
[...]
}
[...]
}
Теперь мне интересно: если у меня есть такой метод, как WatchTaskAsync, который я никогда не хочу ждать (не ожидание этой задачи, по сути, является целью всего этого класса), есть ли разница в создании метода async void в первую очередь?
Я знаю о разнице между ожиданием и сбросом, но в этом конкретном контексте, когда метод вызывается только в том сценарии, где я не хочу ждать его завершения, создаю этот Task объект только для того, чтобы каждый раз отбрасывать его. время не имеет смысла, не так ли?
@freakish Как я уже сказал, суть этого класса в том, что отброшенная задача не имеет значения, не нуждается в мониторинге, никогда не будет выбрасываться и т. д. Задача, которую вы передаете, является актуальной, и она отслеживается и обрабатывается. правильно.
@Fildor Да, я отредактировал фрагмент кода, чтобы сделать это более очевидным :)
Если вам нужна задача «выстрелил и забыл», может быть полезно четко показать, что это и есть предполагаемое поведение. И что вы не использовали «async void», потому что не знали лучшего.





Отказ от Task называется «выстрелил и забыл».
Запуск операции async void называется пожар-и-крушение.
Разница появляется в случае ошибки. Исключение «уволили и забыли» Task никогда не возникнет. Оно навсегда останется вне поля зрения. Исключение операции async void будет повторно создано в захваченном SynchronizationContext , и все известные SynchronizationContext громко распространяют свои исключения, открывая окна сообщений и т. д. В случае отсутствия SynchronizationContext, как в консольном приложении, исключение генерируется повторно. в ThreadPool, что приводит к сбою процесса через несколько миллисекунд после ошибки¹.
Если у вас есть Task и вы хотите, чтобы он воспринимался как async void, проще всего установить await на ThreadPool:
ThreadPool.QueueUserWorkItem(async _ => await task);
Метод ThreadPool.QueueUserWorkItem не понимает асинхронные делегаты, поэтому на самом деле этот асинхронный делегат async void. А поскольку ThreadPool не имеет контекста синхронизации, который async void мог бы захватить, вы получите сбой.
¹ Which sounds bad, but it's arguably better than the alternative. It's better to get a crash with an error message, than to get a non-responsive application that hangs spontaneously without giving any feedback.
Присвоения исключения Task достаточно, чтобы считать его выполненным, даже если эта задача собирается мусором, даже не просматриваясь? Я знал, что исключение, созданное внутри async void, приводит к сбою вашего приложения, но я не знал, что этого не происходит в случае отброшенных async Task возвратов.
@LWChris .NET Framework 4.0 была единственной версией .NET, в которой сбой Task с ненаблюдаемым исключением приводил к сбою процесса. Подробности смотрите в документации события TaskScheduler.UnobservedTaskException. Но даже тогда крах не был мгновенным. Процесс аварийно завершился, когда неисправный Task был собран мусором, что является недетерминированным событием. Это было ужасное дизайнерское решение, и, к счастью, они изменили его в следующем выпуске .NET.
Компилятор сгенерирует для вас задачу независимо от вашего выбора. Таким образом, пока вы пишете опасный код, выигрыша в производительности нет. Настоящая проблема в том, что оба варианта плохи — не следует отбрасывать Task, а хранить его где-то и контролировать. И, возможно, уловить потенциальные исключения и отреагировать на них. В конце концов вам следует подождать, по моему мнению, этот дизайн не очень хорош.