【问题标题】:Efficient Rate Limiting of Async Tasks (N executions per time interval)异步任务的有效速率限制(每个时间间隔执行 N 次)
【发布时间】:2013-12-31 13:26:17
【问题描述】:

在使用 .Net 4.0/4.5 任务时,如何实现有效的速率限制算法(每个时间间隔执行 N 次特定操作)?例如,如果在一秒钟内发送的消息超过 N 条,我希望 SendMessageAsync 阻止(不发送另一条消息)。

        await Task.WhenAll(
                    Enumerable.Range(0, TOTAL_MESSAGES)
                    .Select(async x =>
                    {
                        await clientSession.SendMessageAsync(CreateMessage());
                    }));

我尝试在SendMessageAsync 中使用Task.Delay,但由于等待Task.Delay 立即返回,所以下一条消息将在没有任何阻塞的情况下发送。

public async Task<ResponseMessage> SendMessageAsync(RequestMessage message)
{

    int sleepTime;
    if (throttler.ShouldThrottle(out sleepTime))
    {
        await Task.Delay(sleepTime);
    }

    return await InternalMessageWithoutWaitingAsync(message);
}

我可以将 await Task.Delay(sleepTime) 更改为 Thread.Sleep(sleepTime) 从而在异步之前等待,但我想知道在使用任务时这是否是一个好习惯。

【问题讨论】:

  • 我只会在“节流器”发出“开始”信号时开始下一个任务。我认为这比开始所有任务并立即等待大多数任务更好。
  • 基于时间的操作,如限制在一个时间间隔内最好使用Rx而不是直接使用async
  • @RobertMircea 使用Thread.Sleep 绝对是一种不好的做法(浪费资源)。我没有得到您对next message will be sent without any blocking 的关注。这是否意味着您需要按顺序执行所有任务?如果你的场景真的很复杂,你可以看看实现你自己的TaskSchedulerHow to: Create a Task Scheduler That Limits Concurrency
  • @outcoldman 正如@StephenCleary 所提到的,问题是关于时间间隔的节流,而不是限制某个时刻的并行操作(并发)的数量。 next message will be sent without any blocking 表示在不考虑我试图达到的 1 秒间隔内的速率限制的情况下发送下一条消息。
  • @RobertMircea 然后你可以实现你自己的TaskScheduler(如果你不想使用Rx

标签: c# asynchronous async-await c#-5.0


【解决方案1】:

async 代码中使用Thread.Sleep 不是一个好习惯。

你可能会像这样得到你需要的东西:

private SemaphoreSlim _semaphore = new SemaphoreSlim(N);
public async Task<ResponseMessage> SendMessageAsync(RequestMessage message)
{
  await _semaphore.WaitAsync();
  try
  {
    return await InternalMessageWithoutWaitingAsync(message);
  }
  finally
  {
    ReleaseSemaphoreAfterDelayAsync();
  }
}

private async Task ReleaseSemaphoreAfterDelayAsync()
{
  await Task.Delay(TimeInterval);
  _semaphore.Release();
}

【讨论】:

  • 我用这个 sn-p 的代码生成了 400K 的消息。问题是创建了许多消耗 800mb RAM 的任务,而实际上只有 N 个正在运行处理消息,其余的被信号量阻塞。如何在等待处理完成时停止创建新任务? ``` await Task.WhenAll( Enumerable.Range(0, 400000) .Select(async x => { var responsePdu = await client.SendPduAsync(NewMessage()); [...] }));```
  • 当我在 ANTS Memory Profiler 中运行 this code 时,我看到 400k 个任务占用了 17,600,000 字节。可能是一些局部变量被提升导致内存压力;请参阅 Stephen Toub 的资源 herehere 了解调整想法。
  • 突然想到:1) 确保 InternalMessageWithoutWaitingAsync 是一个单独的方法,并且 SendMessageAsync 确实只是上面的代码(即,从该方法中删除所有本地变量); 2)您可以在代码中将信号量限制提高; 3) 重新考虑 Rx; async 根本不是为此而设计的。
  • 感谢您的建议。我重新考虑 Rx 没有问题。我想保留的唯一范例是让调用者有机会使用 Task。该库的设计目的是:如果在 30 秒内没有从服务器返回回复,则任务被取消,任务在与服务器通信错误的情况下捕获异常,或者任务为调用者提供等待服务器回复的选项。生产者将消息“流式传输”到库(在生产中不以任何方式限制预先知道的消息数量)。我希望我可以在 Rx 中保留这一点。
  • @Prabhu:你可以await它。请注意,VS2013 不允许在 finally 中使用 await(或 catch),这样会使代码复杂化。下一个版本的 VS 预计将允许 await 带有 finally 块。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-04-11
  • 2011-09-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-11
  • 1970-01-01
相关资源
最近更新 更多