Как обрабатывать ошибки async Start () в TopShelf

У меня есть служба TopShelf, которая использует асинхронный код для подключения к веб-службам и другим серверам приложений.

Если он не может инициализировать свои соединения при запуске, служба должна зарегистрировать некоторые ошибки и корректно остановиться.

Я рассмотрел этот вопрос об остановке TopShelf. когда не выполняются стартовые условия. В этом ответе говорится об использовании TopShelf HostControl для остановки службы.

Однако этот ответ основан на методе ServiceConfigurator<T>.WhenStarted<T>(Func<T, HostControl, bool> start).

Сейчас я настраиваю сервис TopShelf стандартным способом:

x.Service<MyService>(s =>
{
    s.ConstructUsing(() => new MyService());
    s.WhenStarted(s => s.Start());
    s.WhenStopped(s => s.Stop());
});

Однако метод Start() моей службы на самом деле async и определяется следующим образом:

public async void Start()
{
    await Init();
    while (!_canceller.Token.IsCancellationRequested)
    {
        await Poll();
    }
}

Кажется, это нормально работает. Но я использую ключевое слово await в нескольких местах функции. Итак, я не могу просто изменить свой Start() метод, чтобы он принимал HostControl и возвращал bool, потому что мне пришлось бы возвращать Task<bool> из метода async.

В настоящее время я разрешаю исключениям всплывать из функции Start (), чтобы TopShelf мог их видеть и автоматически останавливать службу при появлении исключения. Однако в этом случае исключения полностью не обрабатываются моим кодом, и поэтому я получаю неприятные сообщения об ошибках необработанных исключений в различных журналах, в которые я пишу. Который я бы предпочел заменить красивым сообщением об ошибке и чистым завершением работы службы.

Итак, у меня два вопроса:

  1. Есть ли проблемы с использованием метода async void Start() для TopShelf?
  2. Есть ли способ сделать так, чтобы, если Init() выдает исключение, подробные сведения об исключении корректно регистрируются, а затем служба останавливается, учитывая, что моя служба запускает код async?

person Hydrargyrum    schedule 23.09.2016    source источник


Ответы (1)


Во-первых, async void почти всегда неверен, за исключением некоторых действительно сценариев «выстрелил и забыл». Вы хотите изменить это на async Task.

Тогда иногда вам просто нужно использовать .Wait() на границе между синхронизацией и асинхронным кодом. В этом случае вы, вероятно, захотите переименовать текущий асинхронный Start() метод в StartAsync() и добавить Start() метод, который его вызывает:

public void Start()
{
    StartAsync().Wait();
}

public async Task StartAsync()
{
    await Init();
    while (!_canceller.Token.IsCancellationRequested)
    {
        await Poll();
    }
}

Однако у вас есть другая проблема: метод Start() TopShelf не является методом "Run"(); то есть вы должны вернуться из этого метода, как только ваша служба будет запущена, а не оставаться там, пока служба работает. Учитывая, что вы уже используете async-await, я бы, вероятно, вместо этого не называл Wait() в Start(), а сохранял Task, возвращенный из StartAsync(), а затем, когда вызывается Stop(), сигнализируйте своему Task о прекращении использования этого существующего _canceller и только затем в Stop() вызовите .Wait(), оставив что-то вроде этого:

private Task _serviceTask;

public void Start()
{
    Init().Wait();
    _serviceTask = ExecuteAsync();
}

public void Stop()
{
    _canceller.Cancel();
    _serviceTask.Wait();
}

public async Task ExecuteAsync()
{
    while (!_canceller.Token.IsCancellationRequested)
    {
        await Poll();
    }
}

Я должен добавить, что так, как вы это сделали, вы, вероятно, в некоторой степени уйдете от дел, в том смысле, что ваш метод async Start() вернется в TopShelf, как только он достигнет первого await, но продолжит выполнение. Если ваш Stop() метод вызывает _canceller.Cancel(), тогда ваш асинхронный Start() метод прекратит работу при следующем вызове Poll().

Однако приведенное выше чище, и вам нужно дождаться завершения выполнения последнего Poll(), чего вы не делали раньше. Вы также сможете обрабатывать исключения, как вы упомянули.

Изменить. Я бы также переместил вызов Init() в Start(), как указано выше.

person sellotape    schedule 23.09.2016
comment
Как мне убедиться, что _serviceTask, возвращаемый ExecuteAsync(), действительно запускается, не дожидаясь его в методе Start()? Я не могу ждать его там, потому что для этого потребуется Start() быть асинхронным. В тестовом коде с имитацией Poll() метода, основанного на Task.Delay(), когда я вызываю _serviceTask.Start(), выдается InvalidOperationException с сообщением Start may not be called on a promise-style task.. Если я не жду или не запускаю задачу, кажется, что она остается в состоянии WaitingForActivation? - person Hydrargyrum; 27.09.2016
comment
Неважно, мой тестовый код - отстой. ExecuteAsync () возвращает запущенную задачу, поэтому мне не нужно делать ничего особенного для ее запуска. Я не уверен, почему статус задачи был WaitingForActivation, а не запущен, но задача действительно выполняется и в конечном итоге завершается. - person Hydrargyrum; 27.09.2016
comment
Одна из проблем заключается в том, что если ExecuteAsync генерирует исключение, это никогда не будет замечено? - person Peter; 31.01.2018
comment
@Peter, как вы, наверное, знаете, исключения в ExecuteAsync будут ждать, пока кто-нибудь не вызовет _serviceTask.Result или не проверит _serviceTask.Excetpions - person inwenis; 26.09.2018