如何在创建特定数量的任务\ Threads后等待特定的时间?

时间:2015-09-21 12:19:28

标签: c# multithreading parallel-processing

我有一个要求,我可以在一秒钟内击中API 5次。如果我必须总共发出50个请求,我想先发出前5个请求,然后等待1秒,然后再用另一批5个请求命中API。我尝试使用线程池以及并行任务库For \ Foreach循环和任务类但我无法获得一个顺序计数器,它会告诉我已经创建了5个任务。 以下是我想要做的一个示例:

List<string> str = new List<string>();
for (int i = 0; i <= 100; i++)
{
    str.Add(i.ToString());
}

Parallel.ForEach(str, new ParallelOptions { MaxDegreeOfParallelism = 5 },
(value, pls, index) =>
{
    Console.WriteLine(value);// simulating method call
    if (index + 1 == 5)
    {
        // need the main thread to sleep so next batch is 
        Thread.Sleep(1000);
    }
});

5 个答案:

答案 0 :(得分:3)

由于您使用的是.NET 4.0(并且希望您至少使用VS2012),因此可以使用Microsoft.Bcl.Async来获取async-await功能。

一旦这样做,您就可以轻松地异步查询API端点(不需要额外的线程),并使用AsyncSemaphore(请参阅下面的实现)来限制您同时执行的请求数。 / p>

例如:

public readonly AsyncSemaphore = new AsyncSemaphore(5);
public readonly HttpClient httpClient = new HttpClient();
public async Task<string> LimitedQueryAsync(string url)
{
    await semaphoreSlim.WaitAsync();
    try
    {
        var response = await httpClient.GetAsync(url);
        return response.Content.ReadAsStringAsync();
    }
    finally
    {
        semaphoreSlim.Release();
    }
}

现在您可以这样查询:

public async Task DoQueryStuffAsync()
{
    while (someCondition)
    {
        var results = await LimitedQueryAsync(url);

        // do stuff with results
        await Task.Delay(1000);
    }
}

修改 正如@ScottChamberlain指出的那样,{4}在.NET 4中是不可用的。您可以使用SemaphoreSlim,如下所示:

AsyncSemaphore

答案 1 :(得分:1)

如果已经限制为每秒5次,那么并行运行有多重要?这是尝试的不同观点(未经过编译测试)。我们的想法是节制每一个,而不是限制批量。

foreach(string value in values)
{
  const int alottedMilliseconds = 200;
  Stopwatch timer = Stopwatch.StartNew();

  // ...

  timer.Stop();
  int remainingMilliseconds = alottedMilliseconds - timer.ElapsedMilliseconds;
  if(remainingMilliseconds > 0)
  {
    // replace with something more precise/thread friendly as needed.
    Thread.Sleep(remainingMilliseconds);
  }
}

或者本着您原始要求的精神。使用扩展方法扩展您的解决方案,该方法将列表分成5 ...

的块
public static IEnumerable<List<T>> Partition<T>(this IList<T> source, Int32 size)
{
  for (int i = 0; i < Math.Ceiling(source.Count / (Double)size); i++)
  {
    yield return new List<T>(source.Skip(size * i).Take(size));
  }
}

使用此扩展在外部循环内调用Parallel.ForEach,然后在每个外部循环的末尾应用相同的计时器方法。像这样......

foreach(IEnumerable<string> batch in str.Partitition(5))
{
  Stopwatch timer = Stopwatch.StartNew();

  Parallel.ForEach(
    batch, 
    new ParallelOptions { MaxDegreeOfParallelism = 5 },
    (value, pls, index) =>
    {
      Console.WriteLine(value);// simulating method call
    });

  timer.Stop();
  int remainingMilliseconds = 5000 - timer.ElapsedMilliseconds;
  if(remainingMilliseconds > 0)
  {
    // replace with something more precise/thread friendly as needed.
    Thread.Sleep(remainingMilliseconds);
  }
}

答案 2 :(得分:0)

以下是两种方法。无论从哪种方式,您都可以获得所需的测试配置。 不仅代码简洁,而且无需锁即可实现。

1)递归

您必须在每批5个请求中发出50个请求。这意味着以1秒的间隔总共10批5个请求。定义实体,让:

  • HitAPI()是一次调用API的线程安全方法;
  • InitiateBatch()是启动一批 5 个线程来点击API的方法,

然后,示例实现可以是:

private void InitiateRecursiveHits(int batchCount)
{
    return InitiateBatch(batchCount);
}

只需使用batchCount = 10调用上述方法,它就会调用以下代码。

private void InitiateBatch(int batchNumber)
{
    if (batchNumber <= 0) return;
    var hitsPerBatch = 5;
    var thisBatchHits = new Task[hitsPerBatch];
    for (int taskNumber = 1; taskNumber <= hitsPerBatch; taskNumber++)
         thisBatchHits[taskNumber - 1] = Task.Run(HitAPI);
    Task.WaitAll(thisBatchHits);
    Thread.Sleep(1000); //To wait for 1 second before starting another batch of 5
    InitiateBatch(batchNumber - 1);
    return
}

2)迭代

这比第一种方法简单。只是以迭代的方式做递归方法......

private void InitiateIterativeHits(int batchCount)
{
    if (batchCount <= 0) return;
    // It's good programming practice to leave your input variables intact so that 
    // they hold correct value throughout the execution
    int desiredRuns = batchCount;
    var hitsPerBatch = 5;
    while (desiredRuns-- > 0)
    {
        var thisBatchHits = new Task[hitsPerBatch];
        for (int taskNumber = 1; taskNumber <= hitsPerBatch; taskNumber++)
            thisBatchHits[taskNumber - 1] = Task.Run(HitAPI);
        Task.WaitAll(thisBatchHits);
        Thread.Sleep(1000); //To wait for 1 second before starting another batch of 5
    }
}

答案 3 :(得分:0)

我会使用Microsoft的Reactive Framework(NuGet&#34; Rx-Main&#34;)。

这就是它的样子:

var query =
    Observable
        .Range(0, 100)
        .Buffer(5)
        .Zip(Observable.Interval(TimeSpan.FromSeconds(1.0)), (ns, i) => ns)
        .SelectMany(ns =>
            ns
                .ToObservable()
                .SelectMany(n =>
                    Observable
                        .Start(() =>
                        {
                            /* call here */
                            Console.WriteLine(n);
                            return n;
                        })));

然后你会像这样处理结果:

var subscription =
    query
        .Subscribe(x =>
        {
            /* handle result here */
        });

如果您需要在自然完成之前停止请求,只需拨打subscription.Dispose();

很好,很干净,也很有说服力。

答案 4 :(得分:-1)

也许:

while(true){
   for(int i = 0; i < 5; i++)
       Task.Run(() => { <API STUFF> });
   Thread.Sleep(1000);
}

我不确定这样一直调用task.run是否有效。