始终在Windows服务上运行线程

时间:2014-07-28 18:23:52

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

我正在编写一个Windows服务,它将启动多个工作线程,这些线程将监听Amazon SQS队列并处理消息。将有大约20个线程收听10个队列。

线程必须始终在运行,这就是为什么我倾向于实际使用实际线程作为工作循环而不是线程池线程。

这是一个顶级实现。 Windows服务将启动多个工作线程,每个线程都会收听它的队列和进程消息。

protected override void OnStart(string[] args)
{
     for (int i = 0; i < _workers; i++)
   {
      new Thread(RunWorker).Start();
   }
}

以下是工作的实施

public async void RunWorker()
{
  while(true)
  {
    // .. get message from amazon sqs sync.. about 20ms
    var message = sqsClient.ReceiveMessage();

    try
    {
       await PerformWebRequestAsync(message);
       await InsertIntoDbAsync(message);
    }
    catch(SomeExeception)
    {
       // ... log
       //continue to retry
       continue;
    }
    sqsClient.DeleteMessage();
  }
}

我知道我可以使用Task.Run执行相同的操作并在线程池线程上执行它而不是启动单个线程,但是我没有看到原因,因为每个线程将始终运行。

您认为此实施有任何问题吗?让线程始终以这种方式运行是多么可靠,我该怎么做才能确保每个线程始终在运行?

2 个答案:

答案 0 :(得分:3)

现有解决方案存在的一个问题是,即使在新线程(即RunWorker)上,您也会以一种即发即弃的方式调用new Thread(RunWorker).Start()

RunWorkerasync方法,当执行点点击第一个await(即await PerformWebRequestAsync(message))时,它将返回给调用者。如果PerformWebRequestAsync返回待处理任务,RunWorker将返回,您刚开始的新线程将终止。

我认为你根本不需要新线程,只需使用AmazonSQSClient.ReceiveMessageAsyncawait结果即可。另一件事是你不应该使用async void方法,除非你真的不关心跟踪异步任务的状态。请改用async Task

您的代码可能如下所示:

List<Task> _workers = new List<Task>();
CancellationTokenSource _cts = new CancellationTokenSource();

protected override void OnStart(string[] args)
{
  for (int i = 0; i < _MAX_WORKERS; i++)
  {
    _workers.Add(RunWorkerAsync(_cts.Token)); 
  }
}

public async Task RunWorkerAsync(CancellationToken token)
{
  while(true)
  {
    token.ThrowIfCancellationRequested();

    // .. get message from amazon sqs sync.. about 20ms
    var message = await sqsClient.ReceiveMessageAsync().ConfigureAwait(false);

    try
    {
       await PerformWebRequestAsync(message);
       await InsertIntoDbAsync(message);
    }
    catch(SomeExeception)
    {
       // ... log
       //continue to retry
       continue;
    }
    sqsClient.DeleteMessage();
  }
}

现在,要停止所有待处理的工作人员,您可以简单地执行此操作(从主要&#34;请求调度程序&#34;线程):

_cts.Cancel();
try
{
    Task.WaitAll(_workers.ToArray()); 
}
catch (AggregateException ex) 
{
    ex.Handle(inner => inner is OperationCanceledException);
}

注意,ConfigureAwait(false)对于Windows服务是可选的,因为默认情况下初始线程上没有同步上下文。但是,我保持这种方式使代码独立于执行环境(对于存在同步上下文的情况)。

最后,如果由于某种原因你不能使用ReceiveMessageAsync,或者你需要调用另一个阻止API,或者只是做一些CPU密集型工作开始 {{1只需用RunWorkerAsync包裹它(而不是包裹整个Task.Run):

RunWorkerAsync

答案 1 :(得分:2)

嗯,对于我来说,我会在服务中使用CancellationTokenSource实例化并传递给工作人员。你的while语句将成为:

while(!cancellationTokenSource.IsCancellationRequested)
{
  //rest of the code
}

这样您就可以使用OnStop服务方法取消所有工作人员。

此外,您应该注意:

  1. 如果您正在使用线程外部的线程状态,则可能会引发ThreadStateExceptionThreadInterruptedException或其中一个线程状态。所以,你想要处理一个正确的线程重启。
  2. 工作人员是否需要在迭代之间不间断地运行?我会在那里睡觉(甚至几毫秒),这样他们就不会让CPU保持空虚。
  3. 您需要处理ThreadStartException并重启工作人员(如果发生)。
  4. 除此之外,只要服务运行(每天,几周,几个月),这10个踏板都无法运行。