我正在编写一个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执行相同的操作并在线程池线程上执行它而不是启动单个线程,但是我没有看到原因,因为每个线程将始终运行。
您认为此实施有任何问题吗?让线程始终以这种方式运行是多么可靠,我该怎么做才能确保每个线程始终在运行?
答案 0 :(得分:3)
现有解决方案存在的一个问题是,即使在新线程(即RunWorker
)上,您也会以一种即发即弃的方式调用new Thread(RunWorker).Start()
。
RunWorker
是async
方法,当执行点点击第一个await
(即await PerformWebRequestAsync(message)
)时,它将返回给调用者。如果PerformWebRequestAsync
返回待处理任务,RunWorker
将返回,您刚开始的新线程将终止。
我认为你根本不需要新线程,只需使用AmazonSQSClient.ReceiveMessageAsync
和await
结果即可。另一件事是你不应该使用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
服务方法取消所有工作人员。
此外,您应该注意:
ThreadStateException
或ThreadInterruptedException
或其中一个线程状态。所以,你想要处理一个正确的线程重启。ThreadStartException
并重启工作人员(如果发生)。 除此之外,只要服务运行(每天,几周,几个月),这10个踏板都无法运行。