Для ответа на вопрос несколько не хватает информации - как у вас организована реакция на отмену IHostApplicationLifetime.ApplicationStoping (что и как делает регистрируемый callback при отмене), какие сервисы вы используете и как они реагируют на отмену того же маркера.
И IMHO информацию лучше собирать методом проб и ошибок. Самое первое, что вы можете попробовать - это такой вариант. Во-первых, нужно зарегистрировать свой callback последним. А для этого нужно вызвать конструкторы всех сервисов в надежде, чо если они и регистрируют callback на этот маркер, то делают это у себя в конструкторах и только потом регистрировать свой callback (если вы эти сервисы получаете путем внедрения зависимостей через конструктор, это получится автоматически). Во-вторых, нужно выполнить всю работу в своем callback синхронно, в частности, если сообщения отправляются сервисами асинхронно, то подождать завершение каждого (Tasl.WaitAll вам в помощь), а не ждать каждый через await (или явно - используя ContinueWith - но сейчас так никто не делает).
Идея рассчитана на то, что реализация IHostApplicationLifeTime отменяет (ЕМНИПпроверено в исходниках) этот маркер простым Cancel и ждет завершения отмены (и в документации об этом ожидании смутно упомянуто, т.е., это не хак, зависящий от реализации) , а Cancel вызывает все зарегистрированные callback синхронно, в порядке, обратном регистрации (это ЕМНИП тоже документированнное поведение).
Попробуйте для начала так, прежде чем применять более крутые меры (например, подменять реализации IHostLifeTime и IHostApplicationLifetime(UPD: погорячился, эту реализацию подменять нельзя, там обязан быть класс ApplicationLifetime) - технически это реально, но лучше такое оставить на потом).
Если не прокатит, я ответ постараюсь продолжить.
PS По поводу способов закрытия приложения. AFAIK остановка через "красный квадратик" в VS и закрытие окна консоли вызывает просто уничтожение процесса, и это не перехватывается (могу ошибаться, конечно). А вот закрытие по Ctrl+C отлично перехватывается ConsoleLifeTime (обычно испольуемая для консольных приложений реализация IHostLifetime), так что именно этот способ закрытия можно считать штатным. А вообще по жизни, 100% срабатывающих способов реакции на прекращение работы приложения нет - ибо прекращение может быть вызвано такой причной, как пропадание питания (особенно - если вследствие перерубания кабелей топором ;-) ).