鉴于班级结构:
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
),但找不到任何可以帮助我做我正在尝试做的事情
答案 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);
}
它只是在返回第一个结果之前请求前两个结果,然后始终保持一个结果请求在收益之前。