我有一个使用异步代码连接到Web服务和其他应用程序服务器的TopShelf服务。
如果它无法在启动时初始化其连接,则该服务应记录一些错误并正常停止。
我已经看过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
,因为我必须从{{1}返回Task<bool>
方法。
我目前正在允许异常从Start()函数冒出来,以便TopShelf可以看到它们并在异常冒泡时自动停止服务。但是,我的代码完全没有处理异常,因此我在写入的各种日志中最终出现了令人讨厌的未处理异常错误消息。我希望用一个很好的错误消息和一个干净的服务关闭来替换它。
所以,我有两个问题:
async
方法有什么问题吗?async void Start()
抛出异常,优先记录异常详细信息然后服务停止,假设我的服务运行Init()
代码?答案 0 :(得分:11)
首先,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,我可能不会在Wait()
中调用Start()
,而是保存从Task
返回的StartAsync()
,然后保存{{1} }}被调用,表示您的Stop()
停止使用现有的Task
,而仅在_canceller
调用Stop()
时发出信号,为您留下一些信息像这样:
.Wait()
我应该添加你的方式,你可能会在一定程度上放弃一些东西,因为你的async 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()
后立即返回TopShelf 1}},但会继续执行。如果您的await
方法调用Stop()
,那么您的异步_canceller.Cancel()
方法将在下次调用Start()
时终止。
然而,上面的内容更清晰,你必须能够等到最后Poll()
完成执行,而你之前没有。正如你所提到的,你也可以处理异常。
修改强>
我也会将Poll()
来电转移到Init()
,如上所述。