4

I'm creating a system which will spawn a lot of worker tasks and in some point of my app I'm doing a Task.WaitAny to retrieve the first finished task.

My concern is about how many tasks can I send to WaitAny, I know WaitHandle's WaitAny/All only can support up to 64 wait handles and I'm not sure if the underlying mechanism on Task.WaitAny uses internally wait handles to wait for a finished task and thus these limits apply to them.

Is there any limit to how many tasks can be passed to Task.WaitAny like with WaitHandles or there's no limit?

Cheers.

Gusman
  • 14,905
  • 2
  • 34
  • 50
  • Perhaps you should consider using TPL action blocks (http://stackoverflow.com/a/37975762/982639) for this purpose, and a manual reset event (https://msdn.microsoft.com/en-us/library/system.threading.manualresetevent(v=vs.110).aspx) / auto-reset events (https://msdn.microsoft.com/en-us/library/system.threading.autoresetevent(v=vs.110).aspx) – Alexandru Sep 06 '16 at 17:10
  • I don't see how this can help or benefit on my situation (I know I gave very little info of my project), also, I don't want to add a library (which I don't even know if it's supported by .net Core which my project targets) if I can just use the framework itself. – Gusman Sep 06 '16 at 17:14
  • That's the problem essentially, its hard for me to give you a good answer with little information, but I have to say that if you have to add `async` to the mix one day, you should consider using TPL...that's just from my experience though. In any case, you won't need to worry about limits with action blocks...and if you thread the code properly you will be able to wait for all action blocks to complete, which makes your problem go away... – Alexandru Sep 06 '16 at 17:24
  • Hmmm, Task.WaitAny already uses async, all my code is really async... And I don't want to wait all actions to continue (that's stated on my question), I want to get the first one in finish, My code is a task queue, whenever a task finishes it must take some actions, so I can't wait for all to finish or the purpose of this queue will be defeated. – Gusman Sep 06 '16 at 17:28
  • Right, so - my idea was, in the logic block where you do your task action, in the code right after it completes the action or logic, you just add some more code to *take some action* as you put it, does that not suffice? If you wanted to cancel all blocks here you could do that too, with a cancellation token... – Alexandru Sep 06 '16 at 17:41
  • No, that would be a very big overhead as it will create a ton of lambdas, my system can perfectly have 10k async tasks running on parallel (this is for a BIG system with near a hundred server spawned and this is for creating remote tasks between servers) and 10k lambdas waiting to finish for sure will eat lots of ram, while just waiting for one and executing then the correspondent code will consume nothing. – Gusman Sep 06 '16 at 17:44
  • It won't *create a ton of lambdas*, I'm pretty sure the whole purpose of action blocks is to work on some items in queue fashion and then run them through a lambda expression. In any case, test your code and baseline it and see how the performance is...I think you're making some untrue assumptions here. – Alexandru Sep 06 '16 at 17:48
  • if I have 10k action blocks, each action block will have it's own lambda to finish that task, am I incorrect? Maybe there's something I don't know about lambdas, I always thought each time I use a lambda a new instance is created, am I wrong? – Gusman Sep 06 '16 at 17:52
  • You can add degrees of parallelism to action blocks by setting `MaxDegreeOfParallelism`. There is a default built in, so, ideally what I do is set it to the number of logical cores on my system. This ensures I get maximum CPU throughput, which is usually what you would want anyways and is usually your limit. – Alexandru Sep 06 '16 at 17:59

2 Answers2

5

From what I see, it internally works by chaining a continuation to all the tasks: http://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/TaskFactory.cs,db51a91904616672
(with internal optimizations, as well as some code to handle the racing condition when multiple tasks end at once)

Therefore, the limitations on WaitHandles shouldn't apply here.

Kevin Gosse
  • 38,392
  • 3
  • 78
  • 94
4

Internally Task.WaitAny is calling TaskFactory.CommonCWAnyLogic which is scheduling a continuation on each task you pass in, that continuation marks which index in the array it was and allows the task to complete. It uses the inernal class CompleteOnInvokePromise which could be thought of as kind of a specialized TaskCompletionSource.

Because it is using task continuations and not wait handles the wait handle limit should not apply.

Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431