Как определить, в каком порядке IHostedServices вызываются при завершении работы?

#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 может использовать этот подход, и это означает, что это безопасный подход и для моей библиотеки.