我需要将一些可以随时到达的请求放入队列中,以使每个任务仅在前一个任务结束时才开始。问题是,为此目的使用锁定是个好主意吗?它会产生任何不良影响吗?我期望的排队行为是否会因此而导致?
更具体地说,请考虑以下代码:
private int MyTask() {
...
}
private object someLock = new object();
public Task<int> DoMyTask() {
return Task.Run(() =>
{
lock (someLock)
{
return MyTask();
}
});
}
public void CallMyTask() {
var result = await DoMyTask();
}
请注意,CallMyTask()
将在任何时间(可能同时被调用)
答案 0 :(得分:3)
为此目的使用锁定是个好主意吗?它会产生任何不良影响吗?我期望的排队行为是否会因此而导致?
在这里锁定不是一个好的解决方案。不利的影响是,从工作进入队列之时起直到工作完成为止,它都会阻塞线程池线程。因此,如果您的代码将1000个请求排队,则它将调用Task.Run
1000次,并可能耗尽该数量的线程池线程,每个线程池除了等待锁外什么都不做。
此外,锁也不是严格的FIFO。它们只是多数排序FIFO。这是因为严格意义上的FIFO锁会引起其他问题,例如锁车队; the links in this issue have some great discussion about lock "fairness" (i.e., FIFO behavior)。
所以,我建议一个实际的队列。您可以使用TPL Dataflow中的ActionBlock<T>
充当真正的队列。由于您的请求具有结果,因此可以将TaskCompletionSource<T>
用于排队代码以获取结果。 TaskCompletionSource<T>
是一个“异步信号”-在这种情况下,我们正在使用它来通知调用代码其特定的请求已通过队列并已执行。
private ActionBlock<TaskCompletionSource<int>> queue =
new ActionBlock<TaskCompletionSource<int>>(tcs =>
{
try { tcs.TrySetResult(MyTask()); }
catch (Exception ex) { tcs.TrySetException(ex); }
});
每次我们向此TaskCompletionSource<T>
发送queue
时,它将运行MyTask()
并捕获结果(无论是成功还是异常),并将这些结果传递给{{ 1}}。
然后我们可以像这样使用它:
TaskCompletionSource<T>
答案 1 :(得分:1)
我认为锁定几乎是您自己实现此目标的唯一方法,但是.NET框架应该可以在您使用blocking collection和concurrent queue的情况下为您完成此任务。 Blocking集合为您提供了线程安全的生产者/消费者模式的实现。
这是一个按顺序打印数字的示例。
class Program
{
private static BlockingCollection<Task> m_BlockingCollection = new BlockingCollection<Task>(new ConcurrentQueue<Task>());
private static int Counter;
static async Task Main(string[] args)
{
Task.Run(ProcessQueue); //Don't await for this demo!
Task.Run(AddStuffToQueue); //Don't await for this demo!
Console.ReadLine();
m_BlockingCollection.CompleteAdding();
while (!m_BlockingCollection.IsAddingCompleted)
Thread.Sleep(5);
}
private static void AddStuffToQueue()
{
while(true)
m_BlockingCollection.Add(new Task(() => Console.WriteLine(Interlocked.Increment(ref Counter))));
}
private static async Task ProcessQueue()
{
while (!m_BlockingCollection.IsCompleted && m_BlockingCollection.TryTake(out Task task))
ProcessTask(task);
}
private static void ProcessTask(Task task)
{
task.RunSynchronously();
}
}
这可能不是一个完美的例子,但是我敢肯定您的想法。生产者/消费者包装并发队列,因此任务以先进先出(FIFO)的方式执行。
可能有多个消费者用于阻塞收集,但是如果您希望一次处理一件商品,那么演示一个单一的消费者就足够了。
希望对您有帮助!