我有一个异步方法
首先想到的选项是使用具有有限并发性的
但是要使用自定义调度程序运行任务,必须使用只接受
第二个选项是
RequestInternalAsync()
用于向外部资源发出请求,想要编写一个包装器方法来通过减少并行度限制该方法的并发异步请求数量。首先想到的选项是使用具有有限并发性的
TaskScheduler
(如LimitedConcurrencyLevelTaskScheduler
、ConcurrentExclusiveSchedulerPair
等)。但是要使用自定义调度程序运行任务,必须使用只接受
Action <>
的TaskFactory
启动任务, 即不能为仅等待内部方法执行而不阻塞额外线程。第二个选项是
SemaphoreSlim
,它可以完成工作,但在这种情况下,我正在实现自己的节流控制,而不是使用TaskScheduler
。static void Main(string[] args)
{
// TESTING 1
var task1 = Task.WhenAll(Enumerable.Range(1, 10).Select(i => RequestAsyncBad()));
task1.Wait();
// TESTING 2
var task2 = Task.WhenAll(Enumerable.Range(1, 10).Select(i => RequestAsyncBetter()));
task2.Wait();
}
private static Task RequestInternalAsync()
{
return Task.Delay(500);
}
解决方案 #1:
private static readonly ConcurrentExclusiveSchedulerPair _concurrentPair
= new ConcurrentExclusiveSchedulerPair(TaskScheduler.Default, 2);
public static Task RequestAsyncBad()
{
// Dumb: Because TaskFactory doesn't provide an overload which accepts another task, only action.
// As result, we blocking a thread to just wait until the inner task finishes.
return Task.Factory.StartNew(() => RequestInternalAsync().Wait(),
CancellationToken.None, TaskCreationOptions.DenyChildAttach, _concurrentPair.ConcurrentScheduler);
}
解决方案 #2(更好):
private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(2);
public static async Task RequestAsyncBetter()
{
// Here we don't waste thread-pool thread on waiting for a completion of inner task,
// but instead of using TaskScheduler, implementing a hand-made stuff with semaphore.
await _semaphore.WaitAsync().ConfigureAwait(false);
try
{
await RequestInternalAsync();
}
finally
{
_semaphore.Release();
}
}
如何更优雅地实现此功能?
- 重用TPL的标准
Task
API和TaskScheduler
- 而不会阻塞额外的线程
TaskScheduler
只负责任务的启动时间和位置,而不设计控制已启动任务的生命周期? - Sergey KostrukovTaskScheduler
的设计目的以及为什么TaskFactory
有意只接受委托。感谢您的评论。我真的很喜欢阅读您的博客,并期待着Kindle版的问世 :) - Sergey Kostrukov