Я знаю, что обработка исключений может стать странной, если вы никогда не ждете возвращаемого Task, но если вас на самом деле не волнуют результаты и/или успех асинхронного метода, имеет ли значение, если вы не храните ссылку на задача в любом месте? По сути, я просто хочу выстрелить и забыть метод, и я хочу знать, насколько педантичным я должен быть при этом.
Мой конкретный вариант использования — это асинхронный метод удаления, который ожидает завершения выполнения любых незавершенных задач, прежде чем он удаляет какие-либо HTTPClients.
public async Task Dispose(){
try{
...
}catch{} //might add some minimal logging, but probably wont
}
В порядке убывания лени реализации, которые я рассматривал,
public void DisposeFoo(){
foo.Dispose()//feed the task to the void
}
является ли вышеуказанное более или менее безопасным, чем
private Task DisposeFooTask;
public void DisposeFoo(){
DisposeFooTask= foo.Dispose()//store the task and never touch it again
}
И я также нашел этот подход на SO
static async void FireAndForget(this Task task)
{
try
{
await task;
}
catch (Exception e)
{
// log errors
}
}
public void DisposeFoo(){
foo.Dispose().FireAndForget();
}
Есть ли риск ошибки в foo.Dispose(), избегающей любых перехватов попыток и убивающей мою систему, если я проигнорирую задачу? И если нет, есть ли риск того, что что-то вроде потока, выполняющего foo.Dispose(), откажется от жизни из-за того, что задача больше не находится в области видимости? Или опасность моего ленивого кодирования заключается исключительно в сокрытии исключений?
Исключением из этого правила является то, что вы читаете только заголовки ответа с помощью HttpCompletionOption.ResponseHeadersRead
, и в этом случае ответ не буферизуется и его необходимо удалить.
Что касается IHttpClientFactory, если все это находится в одной группе методов, просто используйте использование там, где вы обычно это делаете (без вреда), ловите и регистрируйте любые ошибки и отбрасывайте. Или, если вам нравится создавать асинхронный метод void FireAndForget, который ловит и отбрасывает, кроме этого, на самом деле больше не о чем говорить.
Если вам все равно, бросит ли foo.Dispose()
или нет, то почему вас волнует, утилизируется foo
или нет? Просто не утилизируйте его вообще. Что самое худшее, что может случиться?
Спасибо за помощь, сэр генерал, мой вариант использования - клиентский объект, который используется везде - нечасто случается что-то, что будет снабжать нашу службу новыми определениями API (возможно, несколькими). Вот почему я хотел нерешительно избавиться от любых ресурсов на старые и почему использование утверждений на самом деле не вариант
Ваш ответ был всем, что мне было нужно, я буду придерживаться ленивого подхода, большое спасибо
Есть ли риск ошибки в foo.Dispose(), избегающей любых перехватов попыток и убивающей мою систему, если я проигнорирую задачу?
Есть неуловимые ошибки, но они закроют процесс, даже если вы попытаетесь обработать ошибки из задачи. Поэтому на практике задача всегда должна завершаться.
И если нет, есть ли риск того, что что-то вроде потока, выполняющего foo.Dispose(), откажется от жизни из-за того, что задача больше не находится в области видимости?
Нет, на задачу будет ссылаться планировщик задач, и это сохранит ее до тех пор, пока ее нельзя будет запустить. И как только он запущен, он поддерживается потоком, который его запускает.
Или опасность моего ленивого кодирования заключается исключительно в сокрытии исключений?
Да, насколько я могу судить, худшим эффектом было бы сокрытие потенциальных исключений.
если вы на самом деле не заботитесь о результатах и/или успехе асинхронного метода, имеет ли значение, если вы нигде не храните ссылку на задачу?
Задача, как правило, не подлежит сборке мусора, пока не завершится; это потому, что есть какой-то обратный вызов (который завершит задачу), который ссылается на эту задачу, и эти обратные вызовы обычно являются корневыми (в терминах GC).
Когда задача игнорируется (то есть выполняется, но не наблюдается), она может стать пригодной для сборки мусора. Если эта задача завершится с исключением, то она поднимет TaskScheduler.UnobservedTaskException
при сборе мусора. Это событие раньше приводило к сбою процесса, но больше не приводит к сбою.
Если вы хотите полностью избежать возникновения события, вы можете использовать оболочку FireAndForget
, чтобы явно отслеживать и игнорировать исключение.
Однако основная проблема с задачами типа «выстрелил и забыл» заключается в том, что ваш код не может знать, когда задача завершится. В этом весь смысл задач «выстрелил-забыл», но удивительно, как много людей думают, что хотят «выстрелил-забыл», но затем хотят убедиться, что задачи выполнены. Это распространенная проблема при определении безопасного выхода из процесса.
Мой конкретный вариант использования — это асинхронный метод удаления, который ожидает завершения выполнения любых незавершенных задач, прежде чем он удаляет какие-либо HTTPClients.
Вызов dispose как задачи «выстрелил и забыл» был бы в порядке. Завершение работы в этом случае не рассматривается, так как ОС все равно очистится.
Однако вообще вызывать dispose в задаче довольно странно. Удаление обычно происходит очень быстро (например, установка одного или двух полей в буквальном смысле), поэтому нет смысла переносить эту работу в фоновый поток.
Это потому, что я жду завершения любых невыполненных вызовов, прежде чем удалять фактический httpClient и отменять tokenSource, но это также то, что мне уже давно интересно
Если вы также запускаете и забываете сами эти вызовы API, то вам следует знать, что они могут быть отменены при выходе из вашего приложения. Таким образом, заставляя их запустить и забыть, вы заявляете, что вам все равно, завершатся ли они. Что мне кажется гораздо опаснее.
Если вы используете IHttpClientFactory, в удалении нет необходимости, новая реализация очищает все ресурсы. Во-вторых, не имеет значения, если вы не ждете, вам просто нужно помнить об ошибках