我有这段代码
List<string> myList = new List<string>();
myList.AddRange(new MyClass1().Load());
myList.AddRange(new MyClass2().Load());
myList.AddRange(new MyClass3().Load());
myList.DoSomethingWithValues();
异步运行任意数量的Load()方法的最佳方法是什么,然后确保在所有异步线程完成时运行DoSomethingWithValues()(当然每次回调发生时都不会增加变量并等待== 3)
答案 0 :(得分:8)
我个人最喜欢的是:
List<string> myList = new List<string>();
var task1 = Task.Factory.StartNew( () => new MyClass1().Load() );
var task2 = Task.Factory.StartNew( () => new MyClass2().Load() );
var task3 = Task.Factory.StartNew( () => new MyClass3().Load() );
myList.AddRange(task1.Result);
myList.AddRange(task2.Result);
myList.AddRange(task3.Result);
myList.DoSomethingWithValues();
答案 1 :(得分:5)
PLINQ怎么样?
var loadables = new ILoadable[]
{ new MyClass1(), new MyClass2(), new MyClass3() };
var loadResults = loadables.AsParallel()
.SelectMany(l => l.Load());
myList.AddRange(loadResults);
myList.DoSomethingWithValues();
编辑:如Reed Copsey所指出的,更改选择为SelectMany。
答案 2 :(得分:1)
Ani的概念解决方案可以更简洁地编写:
new ILoadable[] { new MyClass1(), new MyClass2(), new MyClass3() }
.AsParallel().SelectMany(o => o.Load()).ToList()
.DoSomethingWithValues();
这是我首选的解决方案:声明式(AsParallel)和简洁。
当以这种方式书写时,里德的解决方案如下:
new ILoadable[] { new MyClass1(), new MyClass2(), new MyClass3() }
.Select(o=>Task.Factory.StartNew(()=>o.Load().ToArray())).ToArray()
.SelectMany(t=>t.Result).ToList()
.DoSomethingWithValues();
请注意,两个 ToArray
调用可能都是必要的。如果o.Load
是惰性的(通常它可以是YMMV),则第一次调用是必要的,以确保在后台任务中完成对o.Load
的评估。第二次调用是必要的,以确保在调用SelectMany
之前已完全构造任务列表 - 如果您不这样做,那么SelectMany
将尝试仅在必要时迭代其源 - 即它不会在它必须之前迭代到第二个任务,并且直到第一个任务的Result
被计算出来。实际上,您正在启动任务,但随后又一个接一个地执行它们 - 将后台任务重新转换为严格的顺序执行。
请注意,第二个声明性较小的解决方案存在更多陷阱,并且需要进行更彻底的分析以确保它是正确的 - 即,这不太可维护,尽管仍然比手动线程更好。顺便提一下,您可以放弃拨打.ToList
的电话 - 这取决于DoSomethingWithValues
的详细信息 - 以获得更好的性能,即您的最终处理可以访问第一个值,因为它们涓涓细流无需等待所有任务或并行枚举完成。而且启动时间更短!
答案 3 :(得分:-1)
除非有令人信服的理由尝试一次性运行它们,否则我建议您只使用一个异步方法运行它们。
令人信服的原因可能是重型磁盘/数据库IO意味着运行多个后台线程实际上允许它们同时运行。如果大多数初始化实际上是代码逻辑,您可能会发现多个线程实际上导致性能降低。