开发者

C#: Different ways to run this code asynchronously?

开发者 https://www.devze.com 2023-02-11 12:32 出处:网络
I have this code List<string> myList = new List<string>(); myList.AddRange(new MyClass1().Load());

I have this code

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

myList.AddRange(new MyClass1().Load());
myList.AddRange(new MyClass2().Load());
my开发者_StackOverflow社区List.AddRange(new MyClass3().Load());

myList.DoSomethingWithValues();

What's the best way of running an arbitrary number of Load() methods asynchronously and then ensuring DoSomethingWithValues() runs when all asynchronous threads have completed (of course without incrementing a variable every time a callback happens and waiting for == 3)


My personal favorite would be:

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();


How about PLINQ?

var loadables = new ILoadable[] 
                { new MyClass1(), new MyClass2(), new MyClass3() };

var loadResults = loadables.AsParallel()
                           .SelectMany(l => l.Load());

myList.AddRange(loadResults);

myList.DoSomethingWithValues();

EDIT: Changed Select to SelectMany as pointed out by Reed Copsey.


Ani's conceptual solution can be written more concisely:

new ILoadable[] { new MyClass1(), new MyClass2(), new MyClass3() }
    .AsParallel().SelectMany(o => o.Load()).ToList()
    .DoSomethingWithValues();

That's my preferred solution: declarative (AsParallel) and concise.

Reed's solution, when written in this fashion, looks as follows:

new ILoadable[] { new MyClass1(), new MyClass2(), new MyClass3() }
    .Select(o=>Task.Factory.StartNew(()=>o.Load().ToArray())).ToArray()
    .SelectMany(t=>t.Result).ToList()
    .DoSomethingWithValues();

Note that both ToArray calls may be necessary. The first call is necessary if o.Load is lazy (which in general it can be, though YMMV) to ensure evaluation of o.Load is completed inside the background task. The second call is necessary to ensure the list of tasks has been fully constructed before the call to SelectMany - if you don't do this, then SelectMany will attempt to iterate over its source only as necessary - i.e. it won't iterate to the second task before it has to, and that's not until the first task's Result has been computed. Effectively, you're starting tasks but then lazily executing them one after the other - turning background tasks back into a strictly sequential execution.

Note that the second, less declarative solution has many more pitfalls and requires a much more thorough analysis to be sure it's correct - i.e., this is less maintainable, though still miles better than manual threading. Incidentally, you may be able to get away with leaving out the calls to .ToList - that depends on the details of DoSomethingWithValues - for even better performance, whereby your final processing can access the first values as they trickle in without needing to wait for all tasks or parallel enumerables to complete. And that's even shorter to boot!


Unless there's compelling reason to try to run them all at once I'd suggest you just run them all in a single asynchronous method.

Compelling reason might be heavy disk/database IO that would mean running more than one background thread would actually allow them to run simultaneously. If most of the initialization is actually code logic, you might find that multiple threads actually result in slower performance.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号