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

Ser*_*kov 11 c# task-parallel-library async-await

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

第一个选项,我想到的是TaskScheduler用有限的并发(LimitedConcurrencyLevelTaskScheduler,ConcurrentExclusiveSchedulerPair等等).

但是要使用自定义调度程序运行任务,我必须使用TaskFactory仅接受的任务启动任务Action<>,即我不能通过不阻止额外的线程来等待执行内部方法来实现.

第二种选择是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);
}
Run Code Online (Sandbox Code Playgroud)

解决方案#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);
}
Run Code Online (Sandbox Code Playgroud)

解决方案#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();
    }
}
Run Code Online (Sandbox Code Playgroud)

更优雅的方式是什么?

  • 重用TaskTPL的标准API和TaskScheduler
  • 而不是阻止额外的线程

usr*_*usr 8

TaskScheduler仅适用于受CPU限制的工作.你的工作不是使用线程.它使用IO完成端口,这意味着您的网络呼叫根本不包含任何线程.没有办法涉及TaskSchedulerIO操作.

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

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

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

  • 我不会这么说.注意,有两种任务(这很令人困惑!):CPU-work(由最终运行的委托支持的任务)和TaskCompletionSource支持的"其他工作".只有第一种情况使用TaskScheduler(它总是使用一个).所以我要说TaskScheduler负责通过在它认为合适的时间和地点运行委托来完成委托支持的任务. (2认同)