c#限制foreach迭代的速率

时间:2016-01-22 23:15:28

标签: c# system.reactive rate-limiting

基本上我试图能够限制列表迭代的执行速度。

我非常喜欢使用RX的想法,因为我可以构建它的顶部,并且有一个更优雅的解决方案,但它不必使用RX完成。

我已经在比我更聪明的人的帮助下制定了这个。我的问题是我希望能够说一些收集.RateLimitedForEach(比率,功能),并让它最终阻止直到我们已经完成了处理......或者让它成为异步方法。

该功能下方的演示在控制台应用程序中运行,但如果我在foreach之后关闭,它会立即返回。

我有点不知道这是否可以解决,或者我是否应该完全不同

public static void RateLimitedForEach<T>(this List<T> list, double minumumDelay, Action<T> action)
{
    list.ToObservable().Zip(Observable.Interval(TimeSpan.FromSeconds(minumumDelay)), (v, _) => v)
    .Do(action).Subscribe();
}

//rate limits iteration of foreach... keep in mind this is not the same thing as just sleeping for a second
//between each iteration, this is saying at the start of the next iteration, if minimum delay time hasnt past, hold until it has
var maxRequestsPerMinute = 60;
requests.RateLimitedForeach(60/maxRequestsPerMinute,(request) =>   SendRequest(request));

4 个答案:

答案 0 :(得分:4)

  

但不一定要使用RX

以下是如何同步执行此操作:

public static void RateLimitedForEach<T>(
    this List<T> list,
    double minumumDelay,
    Action<T> action)
{
    foreach (var item in list)
    {
        Stopwatch sw = Stopwatch.StartNew();

        action(item);

        double left = minumumDelay - sw.Elapsed.TotalSeconds;

        if(left > 0)
            Thread.Sleep(TimeSpan.FromSeconds(left));
    }
}

以下是如何异步执行此操作(只有潜在的等待是异步的):

public static async Task RateLimitedForEachAsync<T>(
    this List<T> list,
    double minumumDelay,
    Action<T> action)
{
    foreach (var item in list)
    {
        Stopwatch sw = Stopwatch.StartNew();

        action(item);

        double left = minumumDelay - sw.Elapsed.TotalSeconds;

        if (left > 0)
            await Task.Delay(TimeSpan.FromSeconds(left));
    }
}    

请注意,您可以更改异步版本,使其自动异步,如下所示:

public static async Task RateLimitedForEachAsync<T>(
    this List<T> list,
    double minumumDelay,
    Func<T,Task> async_task_func)
{
    foreach (var item in list)
    {
        Stopwatch sw = Stopwatch.StartNew();

        await async_task_func(item);

        double left = minumumDelay - sw.Elapsed.TotalSeconds;

        if (left > 0)
            await Task.Delay(TimeSpan.FromSeconds(left));

    }
}    

如果您需要在每个项目上运行的操作是异步的,这将非常有用。

最后一个版本可以像这样使用:

List<string> list = new List<string>();

list.Add("1");
list.Add("2");

var task = list.RateLimitedForEachAsync(1.0, async str =>
{
    //Do something asynchronous here, e.g.:
    await Task.Delay(500);
    Console.WriteLine(DateTime.Now + ": " + str);
});

现在你应该等task完成。如果这是Main方法,那么您需要同步等待:

task.Wait();

另一方面,如果你在异步方法中,那么你需要像这样异步等待:

await task;

答案 1 :(得分:0)

您需要获得的概念是主线程没有等待您的RateLimitedForEach调用完成。此外 - 在您的控制台应用程序上 - 主线程结束后,流程结束。

这是什么意思?这意味着无论RateLimitedForEach上的观察者是否已完成执行,该过程都将结束。

注意:用户可能仍然强制执行您的应用程序,这是一件好事。如果您希望能够在不挂起UI的情况下等待,则可以使用表单应用程序,如果您不希望用户关闭与该过程相关的窗口,则可以使用服务。

使用任务是我在下面提供的superios solution

请注意,在控制台应用程序上使用“任务”时,您仍需要等待任务以防止主线程在RateLimitedForEach完成其作业之前完成。仍然建议远离控制台应用程序。

如果您坚持继续使用您的代码,您可以调整它以挂起调用线程直到完成:

public static void RateLimitedForEach<T>
(
    this List<T> list,
    double minumumDelay,
    Action<T> action
)
{
    using (var waitHandle = new ManualResetEventSlim(false))
    {

        var mainObservable = list.ToObservable();
        var intervalObservable = Observable.Interval(TimeSpan.FromSeconds(minumumDelay));  
        var zipObservable = mainObservable .Zip(intervalObservable, (v, _) => v);
        zipObservable.Subscribe
        (
            action,
            error => GC.KeepAlive(error), // Ingoring them, as you already were
            () => waitHandle.Set() // <-- "Done signal"
        );

        waitHandle.Wait(); // <--- Waiting on the observer to complete
    }
}

答案 2 :(得分:0)

您的代码非常完美。

请改为尝试:

public static void RateLimitedForEach<T>(this List<T> list, double minumumDelay, Action<T> action)
{
    list
        .ToObservable()
        .Zip(Observable.Interval(TimeSpan.FromSeconds(minumumDelay)), (v, _) => v)
        .Do(action)
        .ToArray()
        .Wait();
}

答案 3 :(得分:-1)

相关问题