.NET 中在单独(单个)线程上管理任务队列的最佳方式 [英] Best way in .NET to manage queue of tasks on a separate (single) thread
问题描述
我知道这些年来异步编程发生了很多变化.我有点尴尬,因为我让自己在 34 岁就生疏了,但我指望 StackOverflow 使我跟上进度.
I know that asynchronous programming has seen a lot of changes over the years. I'm somewhat embarrassed that I let myself get this rusty at just 34 years old, but I'm counting on StackOverflow to bring me up to speed.
我想做的是在一个单独的线程上管理一个工作"队列,但以一次只处理一个项目的方式.我想在这个线程上发布工作,它不需要将任何东西传回给调用者.当然,我可以简单地启动一个新的 Thread
对象并让它在共享的 Queue
对象上循环,使用睡眠、中断、等待句柄等.但我知道事情有从那以后变得更好了.我们有 BlockingCollection
、Task
、async
/await
,更不用说 NuGet 包,它们可能抽象了很多.
What I am trying to do is manage a queue of "work" on a separate thread, but in such a way that only one item is processed at a time. I want to post work on this thread and it doesn't need to pass anything back to the caller. Of course I could simply spin up a new Thread
object and have it loop over a shared Queue
object, using sleeps, interrupts, wait handles, etc. But I know things have gotten better since then. We have BlockingCollection
, Task
, async
/await
, not to mention NuGet packages that probably abstract a lot of that.
我知道什么是最好的..."问题通常不受欢迎,所以我将重新表述为当前推荐的是什么..."使用内置的 .NET 机制来完成这样的事情最好.但是,如果第三方 NuGet 包将事情简化了很多,那也无妨.
I know that "What's the best..." questions are generally frowned upon so I'll rephrase it by saying "What is the currently recommended..." way to accomplish something like this using built-in .NET mechanisms preferably. But if a third party NuGet package simplifies things a bunch, it's just as well.
我考虑了一个 TaskScheduler
实例,其最大并发数固定为 1,但现在似乎有一种不那么笨拙的方法来做到这一点.
I considered a TaskScheduler
instance with a fixed maximum concurrency of 1, but seems there is probably a much less clunky way to do that by now.
背景
具体来说,在这种情况下,我想做的是在 Web 请求期间将 IP 地理定位任务排队.同一个 IP 可能会多次排队等待地理定位,但任务将知道如何检测并在已经解决的情况下尽早跳过.但是请求处理程序只会抛出这些 () =>LocateAddress(context.Request.UserHostAddress)
调用队列并让 LocateAddress
方法处理重复工作检测.我使用的地理定位 API 不喜欢被请求轰炸,这就是为什么我想一次将它限制为一个并发任务.但是,如果允许该方法通过简单的参数更改轻松扩展到更多并发任务,那就太好了.
Specifically, what I am trying to do in this case is queue an IP geolocation task during a web request. The same IP might wind up getting queued for geolocation multiple times, but the task will know how to detect that and skip out early if it's already been resolved. But the request handler is just going to throw these () => LocateAddress(context.Request.UserHostAddress)
calls into a queue and let the LocateAddress
method handle duplicate work detection. The geolocation API I am using doesn't like to be bombarded with requests which is why I want to limit it to a single concurrent task at a time. However, it would be nice if the approach was allowed to easily scale to more concurrent tasks with a simple parameter change.
推荐答案
要创建异步单度并行工作队列,您可以简单地创建一个 SemaphoreSlim
,初始化为 1,然后让在开始请求的工作之前,排队方法 await
获取该信号量.
To create an asynchronous single degree of parallelism queue of work you can simply create a SemaphoreSlim
, initialized to one, and then have the enqueing method await
on the acquisition of that semaphore before starting the requested work.
public class TaskQueue
{
private SemaphoreSlim semaphore;
public TaskQueue()
{
semaphore = new SemaphoreSlim(1);
}
public async Task<T> Enqueue<T>(Func<Task<T>> taskGenerator)
{
await semaphore.WaitAsync();
try
{
return await taskGenerator();
}
finally
{
semaphore.Release();
}
}
public async Task Enqueue(Func<Task> taskGenerator)
{
await semaphore.WaitAsync();
try
{
await taskGenerator();
}
finally
{
semaphore.Release();
}
}
}
当然,要获得固定的并行度,只需将信号量初始化为其他数字即可.
Of course, to have a fixed degree of parallelism other than one simply initialize the semaphore to some other number.
这篇关于.NET 中在单独(单个)线程上管理任务队列的最佳方式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!