【问题标题】:What is the current correct way to start an infinite processing loop in a windows service?在 Windows 服务中启动无限处理循环的当前正确方法是什么?
【发布时间】:2020-01-31 09:36:23
【问题描述】:

我已经对此进行了一段时间的研究,但多年来情况似乎发生了变化,并且有一些相互矛盾的答案。

我的传统做法是,在我的OnStart 方法中,

new Thread(new ThreadStart(QueueProcessorDoWork));

这样它会启动线程然后立即返回。

然后:

private void QueueProcessorDoWork()
        {
            while (_isRunning)
            {
                DoStuff();
                Thread.Sleep(100);
            }
        }

没有办法让 DoStuff() 成为阻塞操作,它基本上必须轮询。

现在,Thread.Sleep 被认为是不好的做法,因为它会锁定线程。

所以研究表明我可以使用计时器。没问题,我会创建一个计时器,并将其设置为每 100 毫秒轮询一次,然后在计时器处理程序中,我将禁用计时器,进行处理,再次重新启用计时器,一切都会好起来的。

不过,我随后阅读了有关将 async/await 与 Task.Delay(100) 一起使用的信息。

听起来不错。我相信我仍然需要创建一个Thread,因为否则 OnStart 方法将不会返回。但是这还不清楚,如果我不等待就调用异步方法,我不知道会发生什么。

所以问题 1 是 - 我应该只调用一个包含无限循环的异步任务而不等待它启动我的 Windows 服务吗?

如果我确实想在线程中使用 async/await,我该如何开始?我的 ThreadStart 可以调用一个函数,而该函数又会执行 Task.Run(async () => Worker()) 或其他什么,我不确定,因为调用者需要被标记为异步?

所以问题 2 是,我将如何在对 new Thread() 的调用中设置异步/等待模式?

最后一个问题是,这两种方法中的哪一种(或两者兼有?)是启动无限处理循环的“正确”方法?

【问题讨论】:

  • 坚持使用计时器。
  • OnStart 方法必须在设置处理循环后立即返回,因为它是 Windows 服务。与许多示例一样,副本创建计时器,然后以无限循环的形式坐在那里。但就我而言,我必须立即从 OnStart 返回。我猜定时器的设置应该在一个新线程上。我不确定 async/await 是否应该参与其中。
  • 您可以触发并忘记一个任务,就像您可以触发并忘记一个线程一样。 Task.Run() 将在另一个线程上运行,一个来自默认工作池。然后您可以使用 Thread.Sleep() 等。但是如果您只有一个线程并且您必须睡觉(或Task.Delay())并且您不希望它在该线程中一次做多件事,那么我可以'从任务中看不到任何好处。

标签: c# multithreading async-await windows-services


【解决方案1】:

现在,Thread.Sleep 被认为是不好的做法,因为它会锁定线程。

是和不是。 Thread.Sleep 不是理想,但如果你知道这段代码总是在桌面机器上运行,那么在进程的生命周期中增加一个线程是相当便宜的。我不会因此而失眠。

所以研究表明我可以使用计时器。

是的,计时器是一个很好的解决方案。

不过,我随后阅读了有关将 async/await 与 Task.Delay(100) 结合使用的信息。

当然,您也可以这样做。在内部,Task.Delay 只是启动一个计时器并在计时器触发时完成任务。所以实际上是一样的。

我相信我仍然需要创建一个线程,否则 OnStart 方法将不会返回。但是这还不清楚,如果我不等待就调用异步方法,我不知道会发生什么。

As soon as an await needs to (asynchronously) wait for an operation to complete, the async method returns。因此,如果您先将循环更改为await Task.Delay,然后再更改为DoStuff,则不需要单独的线程。或者,您可以调用 QueueProcessorDoWork 包裹在 Task.Run 中,这会将其排队到线程池中。

所以问题 1 是 - 我应该只调用一个包含无限循环的异步任务而不等待它启动我的 Windows 服务吗?

这是一种可能性:

var task = Task.Run(() => QueueProcessorDoWork());

请注意,如果循环抛出异常,task 会以异常结束。我建议使用awaits 该任务并在完成后退出服务的“管理”方法:

async Task MainAsync()
{
  try { QueueProcessorDoWork(); }
  catch (Exception ex) { /* log */ }
  finally { /* stop the Win32 service */ }
}

那么问题 2 是,我将如何在对 new Thread() 的调用中设置异步/等待模式?

你没有。 Thread.Sleep 有点不鼓励,但new Thread 绝对不鼓励。除非您使用 COM 互操作,否则不应创建手动线程。

最后一个问题是,这两种方法中的哪一种(或两者兼有?)是启动无限处理循环的“正确”方法?

只做对你有用的事。计时器或Task.RunTask.Delay 包裹一个循环都可以正常工作。

【讨论】:

  • 谢谢,这非常有帮助,尤其是关于管理部分。似乎 Windows 服务将使用 OnStart 方法运行,调用异步任务(未等待)然后返回,因此 Windows 会很高兴服务启动正常。然后可以进行处理。如果您确实将其包装在 try catch 中,即使该函数已经退出,它还会触发 catch 吗?当您在不等待的情况下执行 Task.Run 时,可能会在堆栈上关闭
  • @NibblyPig:来自async 方法的异常被放置在它们的Task 上。当Taskawaited 时,将重新抛出异常。因此,您可以通过将await 放在try/catch 中来捕获异常。局部堆栈变量有一个闭包,但堆栈上方没有任何东西。
  • 啊,所以我肯定必须将我的 Task.Run 放在另一个 Task.Run 中以阻止它阻塞 OnStart 方法,第一个 Task.Run 只会等待长时间运行的任务完成(并且什么都不做)或错误(记录异常)。非常感谢。
  • @NibblyPig:阅读我发布的链接。当await 等待操作时,该方法返回。
  • @NibblyPig:带有await 的方法本身必须是async 方法。当await 等待时,async 方法将返回。这适用于所有方法,包括OnStart
猜你喜欢
  • 2011-06-04
  • 1970-01-01
  • 2013-04-03
  • 1970-01-01
  • 1970-01-01
  • 2010-12-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多