#c# #asp.net-core #application-shutdown #asp.net-core-hosted-services
#c# #asp.net-core #приложение-завершение работы #asp.net-core-hosted-services
Вопрос:
Я поддерживаю пакет интеграции, который позволяет моим пользователям интегрировать мою библиотеку с ASP.NET Ядро. Этот пакет должен быть совместим со всеми версиями ASP.NET Ядро, начинающееся с 2.1. При завершении работы приложения мой пакет интеграции должен иметь возможность выполнять асинхронную очистку и, к сожалению, не может принимать зависимость от IAsyncDisposable
through Microsoft.Bcl.AsyncInterfaces
(см. Ниже).
Поэтому единственный способ, которым это представляется возможным, — зарегистрировать IHostedService
реализацию. Этот StopAsync
метод вызывается при завершении работы:
public sealed class ShutdownHostedService : IHostedService
{
public MyLibaryCleanupObject Obj;
public Task StartAsync(CancellationToken token) => Task.CompletedTask;
public Task StopAsync(CancellationToken token) => this.Obj.CleanupAsync();
}
services.AddSingleton<IHostedService>(new ShutdownHostedService { Obj = ... });
Однако разработчики приложений, конечно, могут добавлять свои собственные IHostedService
реализации, которые могут взаимодействовать с моей библиотекой. Вот почему важно, чтобы моя собственная IHostedService
реализация вызывалась последней. Но здесь кроется проблема.
С введением ASP.NET Разработчики приложений Core 2.1 могут выбирать между использованием нового Microsoft.Extensions.Hosting.Host
и (теперь устаревшего) Microsoft.AspNetCore.WebHost
. При WebHost
завершении IHostedService
работы реализации вызываются в порядке регистрации, тогда как при Host
IHostedService
реализации вызываются в порядке, противоположном порядку регистрации.
Для меня это проблематично, потому что моя размещенная служба должна вызываться последней. Поскольку разработчики приложений могут использовать мой пакет интеграции в своих существующих ASP.NET Основное приложение, которое они все еще могут использовать WebHost
, вот почему важно поддерживать этот сценарий.
Вопрос: Что было бы надежным способом определить, в каком «режиме» ASP.NET Основное приложение запускается, поэтому я могу решить добавить свою размещенную службу первой или последней?
В качестве альтернативы, чтобы предотвратить попадание в ловушку XY-проблемы, я открыт для совершенно других решений, которые решают мою проблему реализации «асинхронного завершения работы».
Обратите внимание на IAsyncDisposable
:
Одно из решений, которое может прийти на ум (как справедливо отмечает Ян в комментариях), — это добавить IAsyncDisposable
одноэлементную регистрацию в ServiceCollection
. Это позволило бы выполнить асинхронную очистку при завершении работы. К сожалению, из-за ограничений (объясненных здесь ) невозможно, чтобы мой пакет интеграции зависел от Microsoft.Bcl.AsyncInterfaces
и, следовательно, не зависел IAsyncDisposable
. Это неудачная ситуация, которая, безусловно, усложняет дело. На самом деле, причина невозможности установить зависимость IAsyncDisposable
— это причина, по которой я ищу альтернативные способы реализации кода асинхронного завершения работы.
Комментарии:
1. Я сильно сомневаюсь, что фоновые службы предназначены для использования таким образом, и я уверен, что порядок, в котором они остановлены, является деталью реализации, поэтому полностью негарантирован и может быть изменен в любое время, поэтому я бы определенно предложил изучить другие способы достижения этой цели. Я бы предложил
IAsyncDisposable
, который поддерживается изначально на Core 3.0 и выше и имеет прокладку для более старых версий черезMicrosoft.Bcl.AsyncInterfaces
пакет NuGet. В качестве альтернативы, спросите непосредственно на ASP.NET Ядро репозитория GitHub.2. Привет @IanKemp, спасибо за ваш комментарий. К сожалению, использование
IAsyncDisposable
отсутствует (я должен был более четко указать это в своем вопросе).IAsyncDisposable
именно из-за этой проблемы я нахожусь в этой ситуации. Новая версия библиотеки, которую я разрабатываю, намеренно удаляет зависимостьMicrosoft.Bcl.AsyncInterfaces
, которая запрещает мне добавлятьIAsyncDisposable
регистрацию вIServiceCollection
.3. В худшем случае, если автоматическое решение не найдено, создайте параметр конфигурации (флаг), который можно использовать для указания желаемого порядка. (просто с головы до ног)
4. Другим вариантом может быть регистрация обработчика
IApplicationLifetime
либоApplicationStopping
илиApplicaionStopped
5. Согласен с Nkosi, использование времени жизни приложения кажется лучшим выбором для такого рода задач, особенно учитывая, что вам нужен только этап очистки , но не фактический запуск. Проблема в том, что события жизненного цикла не поддерживают асинхронную работу. Что могло бы сработать, так это предоставить пользовательский
IHostLifetime
, но это также немного усложняет ситуацию, поскольку вам нужно поддерживать разные сроки службы…
Ответ №1:
Решение, которое я в конечном итоге использую, идентично решению, выбранному Microsoft.Extensions.Hosting.Internal.Host
классом Microsoft, и не предполагает использования IHostedService
экземпляров. Host
Класс содержит следующий Dispose
метод:
public void Dispose()
{
this.DisposeAsync().GetAwaiter().GetResult();
}
Это может вызвать недоумение и может рассматриваться как плохая практика, но не забывайте, что:
- Этот код гарантированно выполняется в контексте ASP.NET Ядро, где этот код не может вызвать взаимоблокировку
- Этот код выполняется только один раз при завершении работы, поэтому производительность здесь не является проблемой.
Вот почему Host
класс Microsoft может использовать этот подход, и это означает, что это безопасный подход и для моей библиотеки.