【问题标题】:How to handle async Start() errors in TopShelf如何处理 TopShelf 中的异步 Start() 错误
【发布时间】:2016-09-24 17:09:17
【问题描述】:

我有一个 TopShelf 服务,它使用异步代码连接到 Web 服务和其他应用程序服务器。

如果它无法在启动时初始化其连接,服务应该记录一些错误并正常停止。

我查看了this question 关于在不满足开始条件时停止 TopShelf 的信息。 This answer 谈到使用 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,因为我必须从async 方法返回Task&lt;bool&gt;

我目前允许从 Start() 函数冒泡异常,以便 TopShelf 可以看到它们并在异常冒泡时自动停止服务。但是,我的代码完全未处理异常,因此我在写入的各种日志中都会收到令人讨厌的未处理异常错误消息。我更愿意用一个不错的错误消息和干净的服务关闭来替换它。

所以,我有两个问题:

  1. 对 TopShelf 使用 async void Start() 方法有什么问题吗?
  2. 如果我的服务运行async 代码,有没有办法让Init() 抛出异常,异常详细信息被正常记录,然后服务停止?

【问题讨论】:

    标签: c# asynchronous async-await topshelf


    【解决方案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();
        }
    }
    

    但是,您还有另一个问题,TopShelf 的Start() 方法不是"Run"() 方法;即,您应该在服务启动后立即从该方法返回,而不是在服务运行时留在那里。鉴于您已经在使用 async-await,我可能不会在Start() 中调用Wait(),而是保存从StartAsync() 返回的Task,然后当调用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();
        }
    }
    

    我应该补充一点,按照你的方式,你可能在某种程度上摆脱了一些事情,因为你的异步 Start() 方法将在它到达第一个 await 时立即返回到 TopShelf,但会继续执行。如果您的Stop() 方法调用_canceller.Cancel(),那么您的异步Start() 方法将在下次调用Poll() 时终止。

    但是上面的内容更简洁,你必须等到最后一个 Poll() 完成执行,这是你以前没有的。正如您所提到的,您还可以处理异常。

    编辑 如上所述,我还将Init() 调用移至Start()

    【讨论】:

    • 如何确保ExecuteAsync() 返回的_serviceTask 实际启动,而不在Start() 方法中等待它?我不能在那里等待它,因为那需要Start() 是异步的。在使用基于Task.Delay() 的模拟Poll() 方法的测试代码中,当我调用_serviceTask.Start() 时,会抛出InvalidOperationException 并带有消息Start may not be called on a promise-style task. 如果我不等待或启动任务,它似乎仍然存在WaitingForActivation 状态?
    • 没关系,我的测试代码很烂。 ExecuteAsync() 返回一个正在运行的任务,所以我不需要做任何特别的事情来启动它。我不确定为什么任务的状态是WaitingForActivation 而不是正在运行,但任务确实运行并最终完成。
    • 一个问题是,如果 ExecuteAsync 抛出异常,这将永远不会被注意到?
    • @Peter 你可能知道ExecuteAsync 中抛出的异常会一直等到有人调用_serviceTask.Result 或检查_serviceTask.Excetpions
    猜你喜欢
    • 2015-04-15
    • 2015-07-18
    • 2019-03-21
    • 1970-01-01
    • 1970-01-01
    • 2013-01-25
    • 1970-01-01
    • 2020-06-29
    • 1970-01-01
    相关资源
    最近更新 更多