预加载下一个IEnumerable <t>值</t>

时间:2013-05-28 13:53:53

标签: c# .net performance parallel-processing task-parallel-library

鉴于班级结构:

public class Foo
{
    public IEnumerable<Bar> GetBars()
    {
        for(int i = 0; i < 1000; i++)
        {
            Thread.Sleep(1000);
            yield return new Bar() { Name = i.ToString() };
        }
    }
}

public class Bar
{
    public string Name { get; set; }
}

我有IEnumerable<Foo>的列表,在Bar方法中检索下一个GetBars()的时间非常慢(上面使用Thread.Sleep(1000)进行了模拟)。

我想做以下事情:

myFoo.AsParallel().SelectMany(foo => foo.GetBars().Select(bar => bar.Name))

但由于延迟,我希望继续为每个Bar预加载下一个Foo值,然后将每个IEnumable<Bar>的{​​{1}}合并到彼此按照他们可以访问的顺序。

我一直在研究Tpl数据流异步nuget库(特别是Foo和较小程度TransformBlock),但找不到任何可以帮助我做我正在尝试做的事情

4 个答案:

答案 0 :(得分:2)

问题是,无论是否并行,您仍然无法开始获取第二个Bar对象,直到您获得第一个IEnumerable对象。如果您通过LINQ功能对每个对象进行长时间运行处理,那么仅使用PLINQ确实有帮助,而不是如果延迟是由基础Task引起的。

一个选项是返回一系列public async Task<Bar> GenerateFoo() { await Task.Delay(1000); return new Bar() { Name = i.ToString() }; } public IEnumerable<Task<Bar>> GetBars() { for(int i = 0; i < 1000; i++) { yield return GenerateFoo(); } } 个对象,这样移动迭代器只需要很少的时间:

Bar

使用该代码意味着只移动迭代器启动生成Bar,而不是等到它完成。完成后,您可以为每个任务添加延续以处理每个Task.WaitAll的处理,或者您可以使用Task.WhenAll或{{1}}等方法等待所有任务完成

答案 1 :(得分:1)

我建议您查看Reactive Extensions (Rx) Library。它基本上允许您在“推送”类型集合(IObservable<T>)上使用LINQ,而不是“拉”类型集合(IEnumerable<T>)。换句话说,您的代码可以在集合中的新项目可用时做出反应。

答案 2 :(得分:1)

您可以编写如下扩展方法,只要它们可用,就会产生条形图(在任何可枚举中)。

myFoo.Select(x=>x.GetBars()).Flatten().Select(bar => bar.Name)

public static class ParallelExtensions
{
    public static IEnumerable<T> Flatten<T>(this IEnumerable<IEnumerable<T>> enumOfEnums)
    {
        BlockingCollection<T> queue = new BlockingCollection<T>();

        Task.Factory.StartNew(() =>
        {
            Parallel.ForEach(enumOfEnums, e =>
            {
                foreach (var x in e)
                {
                    queue.Add(x);
                }
            });
            queue.CompleteAdding();
        });

        return queue.GetConsumingEnumerable();
    }
}

答案 3 :(得分:1)

我最终编写了一个执行完成的IEnumerable<T>的新实现:

public IEnumerator<T> GetEnumerator()
{
    TaskFactory<T> taskFactory = new TaskFactory<T>();
    Task<T> task = null;
    IEnumerator<T> enumerator = Source.GetEnumerator();

    T result = null;
    do
    {
        if (task != null)
        {
            result = task.Result;
            if (result == null)
                break;
        }

        task = taskFactory.StartNew(() =>
        {
            if (enumerator.MoveNext())
                return enumerator.Current;
            else
                return null;
        });
        if (result != null)
            yield return result;
    }
    while (task != null);
}

它只是在返回第一个结果之前请求前两个结果,然后始终保持一个结果请求在收益之前。