Использование асинхронных методов WhenStarted и WhenStopped в TopShelf

Мы используем TopShelf как так, чтобы начать наши услуги. Мы видим несколько странные проблемы, связанные с запуском и остановкой службы, и удивляемся, не связано ли это с нашими асинхронными методами запуска / остановки. Посмотрев на документацию с использованием async, никогда не упоминается. На их страницах github есть одно упоминание, в котором говорится, что вы не должны использовать async таким образом.

Но, сказав это, он компилируется и работает (в основном) нормально. Так это правильно, или я должен использовать .Wait() вместо?

var host = HostFactory.New(hostConfig =>
{
    hostConfig.Service<StreamClient>(serviceConfig =>
    {
        serviceConfig.ConstructUsing(name => new StreamClient());
        serviceConfig.WhenStarted(async tc => await tc.Start());
        serviceConfig.WhenStopped(async tc => await tc.Stop());
    });

    hostConfig.RunAsLocalSystem();

    hostConfig.SetDescription("Stream Client Service");
    hostConfig.SetDisplayName("Stream Client Service");
    hostConfig.SetServiceName("StreamClientService");
});

host.Run();

@Nkosi спросил, как выглядят сигнатуры методов, они асинхронны и запускают внутренних клиентов и процессы.

public async Task Start()
{
    // Dont start again if we are already running, or if we are already in the starting state
    if (this.Running || this.Starting)
    {
        return;
    }

    await this.slackService.SendSlackServiceEvent(ServiceEventType.Starting, serviceName, applicationVersion);

    this.Starting = true;
    this.Stopping = false;

    var configurationBuilder = new ClientConfigurationBuilder();

    ClientConfiguration clientConfiguration;
    if (Constants.UseLocalConnection)
    {
        await this.OnClientDebugMessage($"Using Local Connection");
        clientConfiguration = configurationBuilder.CreateLocalConfiguration();
    }
    else
    {
        await this.OnClientDebugMessage($"Using SQL Connection");
        clientConfiguration = configurationBuilder.CreateSqlConfiguration();
    }

    this.ClusterGrainClient = await this.StartClient(clientConfiguration);

    if (this.ClusterGrainClient == null)
    {
        using (ConsoleColours.TextColour(ConsoleColor.Red))
        {
            await this.OnClientDebugMessage($"Cluster client null, aborting!");
        }

        return;
    }

    this.Running = true;

    await this.OnClientStarted();
    await this.slackService.SendSlackServiceEvent(ServiceEventType.Started, serviceName, applicationVersion);
    this.Starting = false;

}

2 ответа

Решение

Вы по существу делаете async void в тех делегатах, которые стреляют и забывают.

Ссылка Async/Await - Лучшие практики в асинхронном программировании

Обработчики событий являются единственным исключением из этого правила, где это разрешено

Преобразуйте методы start и stop в синхронные методы, которые вызывают асинхронные события, которые можно ожидать внутри.

public void Start() {
    Started += OnStarted; //subscribe to event
    Started(this, EventArgs.Empty); //raise event
}

private event EventHandler Started = delegate { };

private async void OnStart(object sender, EventArgs args) {
    Started -= OnStarted;        
    await StartAsync();
}

public async Task StartAsync() {
    // ...
}

А потом назови старт как обычно

serviceConfig.WhenStarted(_ => _.Start());

что поднимет событие и поток, как ожидалось.

Независимо от того, является ли Topshelf или нет, узел службы Windows запустит службу, а не запустит ее.

Я никогда не пробовал это сам, но вы можете попробовать что-то вроде этого:

public void Start() => this.StartAsync().GetAwaiter().GetResult();
public void Stop() => this.Stop().GetAwaiter().GetResult();

public async Task StartAsync()
{
    // ...
}

public async Task StopAsync()
{
    // ...
}
Другие вопросы по тегам