限制Async方法的并行性,而不是阻塞线程池线程

时间:2014-05-08 19:06:06

标签: c# task-parallel-library async-await

我有一个异步方法RequestInternalAsync(),它向外部资源发出请求,并希望编写一个包装器方法,通过减少并行性来限制对方法的多个并发异步请求。

我想到的第一个选项是具有有限并发性的TaskSchedulerLimitedConcurrencyLevelTaskSchedulerConcurrentExclusiveSchedulerPair等。)

但是要使用自定义调度程序运行任务,我必须使用仅接受TaskFactory的{​​{1}}启动任务,即我不能通过不阻止额外线程等待执行内部方法。

第二个选项是Action<>,它可以完成任务,但在这种情况下,我自己实施了限制,而不是使用SemaphoreSlim

TaskScheduler

解决方案#1:

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);
}

解决方案#2(更好):

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);
}

更优雅的方式是什么?

  • 重用TPL的标准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(); } } API和Task
  • 并且不阻止额外的线程

2 个答案:

答案 0 :(得分:8)

TaskScheduler仅对CPU绑定工作有用。你的工作不是使用线程。它使用IO完成端口,这意味着您的网络呼叫根本不包含任何线程。没有办法让TaskScheduler涉及IO操作。

如果您还不确定:.NET中的异步IO基于使用TaskCompletionSource,它不会以最轻微的方式绑定到线程或调度程序。

SemaphoreSlim是正确的方法。或者,创建一个ServicePoint并设置其最大并发性。仅适用于HTTP次请求。

请注意,如果您发现自己使用的是Wait,那么您应该犹豫并思考自己在做什么。通常,这是一个错误。

答案 1 :(得分:0)

public static async Task RequestAsyncCool()
{
    await Task.Factory.StartNew(async () => {
            await RequestInternalAsync();
        },
        CancellationToken.None, 
        TaskCreationOptions.DenyChildAttach, 
        TaskScheduler.Current);
}

你真的不应该Wait来完成任务。见https://www.google.com/search?q=task+wait+deadlock

您是否考虑过TPL DataFlow?这可能只是你的事情......