I want to execute a library method that does a blocking I/O operation many times (up to 67840 calls). The library does not provide an async version of the method.
Since in most cases the call just waits for a timeout, I want to run multiple calls in parallel. My method is async, therefore it would be good if I could await
the result.
Since the ThreadPool
should not be used for blocking operations, I would like to do the following:
- Start a number of threads (e.g. 1024)
- Run the blocking calls on these threads
await
the completion (e. g. viaTaskCompletionSource
) and process the result of each call in normal Tasks on theTheadPool
Are there existing classes in .NET with which I could achive something like this? I am aware of TaskCreationOptions.LongRunning
, but as far as I can see this would create a new thread for each call.
CodePudding user response:
blocking I/O operation... The library does not provide an async version of the method.
Just from this, you know you won't end up with an "ideal" solution. Ideally, I/O is performed asynchronously. In fact, on Windows, all I/O is performed asynchronously at the OS level, with each synchronous API call just blocking the current thread until that asynchronous operation completes.
So, the first thing you should accept is that you'll need to bend the rules a little.
Since in most cases the call just waits for a timeout, I want to run multiple calls in parallel.
Yes. Parallelism is an appropriate solution. If it were possible to do the I/O asynchronously, then parallelism would not be the appropriate solution, but since the I/O is blocking (and you have no control over that), then parallelism is the best solution you're left with.
My method is async, therefore it would be good if I could await the result.
This doesn't necessarily follow. It's acceptable for asynchronous methods to be partially blocking, as long as that's clearly documented. The asynchronous signature (i.e., "returns a Task
" and has an *Async
suffix) implies that the method may be asynchronous, not that it must be asynchronous.
Personally, I prefer not to do thread offloading in my logic methods, and only do it when calling them from the UI layer (link to my blog).
Since the ThreadPool should not be used for blocking operations
Well, this is one of those rules you can consider bending. The thread pool does work just fine with blocking operations, and in fact it's my first suggested solution.
Start a number of threads (e.g. 1024)... Run the blocking calls on these threads
If you toss out the "I want my own threads" part and just use the thread pool, then the answer is quite simple: Parallel
or PLINQ would work quite nicely. You can set a maximum level of parallelism for both of these approaches, and you can set a larger than normal minimum thread count on the thread pool to scale up the number of threads more quickly if you want.
This does toss a lot of blocking work on the thread pool, which is generally not recommended but can work in some scenarios. Specifically, client applications like console apps or GUI apps would work fine with this. If this is in a web app, though, then you would not want to fill up the thread pool with blocking calls. In that case, I'd actually recommend splitting up the scanning to a separate app using a basic distributed architecture (link to my blog).
await the completion (e. g. via TaskCompletionSource) and process the result of each call in normal Tasks on the TheadPool
If you want to do the parallel work on a separate thread, then you can wrap it in await Task.Run(...)
; mucking around with TCS isn't necessary.