简体   繁体   English

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

[英]Limit parallelism of an Async method and not block a Thread-Pool thread

I have an asynchronous method RequestInternalAsync() which makes requests to an external resource, and want to write a wrapper method which limits a number of concurrent asynchronous requests to the method by reducing parallelism. 我有一个异步方法RequestInternalAsync() ,它向外部资源发出请求,并希望编写一个包装器方法,通过减少并行性来限制对该方法的多个并发异步请求。

First option, that comes to mind is a TaskScheduler with limited concurrency ( LimitedConcurrencyLevelTaskScheduler , ConcurrentExclusiveSchedulerPair etc.). 首先考虑的第一个选项是具有有限并发性的TaskSchedulerLimitedConcurrencyLevelTaskSchedulerConcurrentExclusiveSchedulerPair等)。

But to run a task with a custom scheduler, I have to start the task using a TaskFactory which accepts only Action<> , ie I cannot do it by not blocking an extra thread for just waiting for execution of inner method. 但是要使用自定义调度程序运行任务,我必须使用仅接受Action<>TaskFactory启动任务,即我不能通过不阻止额外线程等待内部方法的执行来实现。

Second option is SemaphoreSlim , it does its job, but in this case I'm implementing throttling myself, instead of using a TaskScheduler . 第二个选项是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);
}

Solution #1: 解决方案#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);
}

Solution #2 (better): 解决方案#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();
    }
}

What is the more elegant way to do this? 更优雅的方式是什么?

  • to reuse standard Task API of TPL and TaskScheduler 重用TPL和TaskScheduler标准Task API
  • and not block an extra thread 而不是阻止额外的线程

TaskScheduler is only useful for CPU-bound work. TaskScheduler仅对CPU绑定工作有用。 Your work is not using threads. 你的工作不是使用线程。 It uses IO completion ports which means that your network call does not hold any threads at all. 它使用IO完成端口,这意味着您的网络呼叫根本不包含任何线程。 There is no way to involve a TaskScheduler for an IO operation. 没有办法让TaskScheduler用于IO操作。

If you're not yet convinced: Async IO in .NET is based on using TaskCompletionSource which is not bound to threads or schedulers in the slightest way. 如果您还不确定:.NET中的异步IO基于使用TaskCompletionSource ,它不会以最轻微的方式绑定到线程或调度程序。

SemaphoreSlim is the right way to do it. SemaphoreSlim是正确的方法。 Or, create a ServicePoint and set its maximum concurrency. 或者,创建一个ServicePoint并设置其最大并发性。 Only applicable for HTTP requests. 仅适用于HTTP请求。

Note, that if you find yourself using Wait you should hesitate and think about what you're doing. 请注意,如果您发现自己使用Wait那么您应该犹豫并思考您正在做什么。 Often, this is a mistake. 通常,这是一个错误。

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

You really shouldn't Wait for Tasks. 你真的不应该Wait任务。 See https://www.google.com/search?q=task+wait+deadlock 请参阅https://www.google.com/search?q=task+wait+deadlock

Did you look into TPL DataFlow ? 你看过TPL DataFlow了吗? It might be just the thing for you... 这可能只是你的事情......

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM