并行ForEach在产生前等待500毫秒

时间:2013-07-15 13:29:12

标签: c# .net plinq

我遇到这种情况:

var tasks = new List<ITask> ...
Parallel.ForEach(tasks, currentTask => currentTask.Execute() );

是否有可能指示PLinq在下一个线程产生之前等待500ms?

System.Threading.Thread.Sleep(5000);

4 个答案:

答案 0 :(得分:6)

您使用Parallel.Foreach完全错误,您应该制作一个特殊的枚举器,其速率限制自己每500毫秒获取一次数据。

由于您未提供任何详细信息,我对DTO的工作方式做了一些假设。

private IEnumerator<SomeResource> GetRateLimitedResource()
{
    SomeResource someResource = null;
    do
    {
        someResource = _remoteProvider.GetData();

        if(someResource != null)
        {
             yield return someResource;
             Thread.Sleep(500);
        }
    } while (someResource != null);
}

这是你的并列应该如何看待

Parallel.ForEach(GetRateLimitedResource(), SomeFunctionToProcessSomeResource);

答案 1 :(得分:3)

已经有一些很好的建议了。我同意其他人的意见,即你正在以不应该使用的方式使用PLINQ。

我的建议是使用System.Threading.Timer。这可能比编写一个返回强制半秒延迟的IEnumerable<>的方法更好,因为您可能不需要等待整整半秒,这取决于自上次API调用以来经过了多长时间。

使用计时器,它将调用你在指定的时间间隔提供它的委托,所以即使第一个任务没有完成,半秒后它会在另一个线程上调用你的委托,所以不会有任何额外的等待。

从您的示例代码中,听起来您有一个任务列表,在这种情况下,我会使用System.Collections.Concurrent.ConcurrentQueue来跟踪任务。队列为空后,关闭计时器。

答案 2 :(得分:2)

您可以改用Enumerable.Aggregate

var task = tasks.Aggregate((t1, t2) =>
                                t1.ContinueWith(async _ =>
                                    { Thread.Sleep(500); return t2.Result; }));

如果您不希望链接的任务,那么Select也会发生重载,假设任务是按照延迟的顺序。

var tasks = Enumerable
              .Range(1, 10)
              .Select(x => Task.Run(() => x * 2))
              .Select((x, i) => Task.Delay(TimeSpan.FromMilliseconds(i * 500))
                                    .ContinueWith(_ => x.Result));

foreach(var result in tasks.Select(x => x.Result))
{
    Console.WriteLine(result);
}

从评论中,更好的选择是保护资源而不是使用时间延迟。

static object Locker = new object();

static int GetResultFromResource(int arg)
{
    lock(Locker)
    {
        Thread.Sleep(500);
        return arg * 2;
    }
}

var tasks = Enumerable
          .Range(1, 10)
          .Select(x => Task.Run(() => GetResultFromResource(x)));

foreach(var result in tasks.Select(x => x.Result))
{
    Console.WriteLine(result);
}

答案 3 :(得分:1)

在这种情况下,具有BlockingCollection<T>的生产者 - 消费者模式怎么样?

var tasks = new BlockingCollection<ITask>();

// add tasks, if this is an expensive process, put it out onto a Task
// tasks.Add(x);

// we're done producin' (allows GetConsumingEnumerable to finish)
tasks.CompleteAdding();

RunTasks(tasks);

使用单个消费者线程:

static void RunTasks(BlockingCollection<ITask> tasks)
{
    foreach (var task in tasks.GetConsumingEnumerable())
    {
        task.Execute();

        // this may not be as accurate as you would like
        Thread.Sleep(500);
    }
}

如果您有权访问.Net 4.5,则可以使用Task.Delay

static void RunTasks(BlockingCollection<ITask> tasks)
{
    foreach (var task in tasks.GetConsumingEnumerable())
    {
        Task.Delay(500)
            .ContinueWith(() => task.Execute())
            .Wait();
    }
}