Задачи не являются 1: 1 с потоками - задачам назначаются потоки для выполнения из пула потоков, и пул потоков обычно поддерживается довольно небольшим (количество потоков == количество ядер ЦП), если задача / поток заблокирован в ожидании длительного синхронного результата - например, синхронного сетевого вызова или файлового ввода-вывода.
Таким образом, выполнение 10 000 задач не должно привести к получению 10 000 реальных потоков. Однако, если каждая из этих задач немедленно переходит в блокирующий вызов, вы можете получить больше потоков, но их все равно не должно быть 10000.
Что может происходить здесь, так это то, что вы перегружаете базу данных SQL слишком большим количеством запросов одновременно. Даже если система настраивает только несколько потоков для тысяч ваших задач, несколько потоков все равно может вызвать сбой, если назначение вызова однопоточное. Если каждая задача выполняет вызов в базу данных SQL, а интерфейс базы данных SQL или сама база данных координирует многопоточные запросы через блокировку одного потока, то все одновременные вызовы будут накапливаться в ожидании, пока блокировка потока не попадет в базу данных SQL для выполнения. , Нет гарантии того, какие потоки будут освобождены для последующего вызова в базу данных SQL, поэтому вы можете легко получить один «неудачный» поток, который начинает ожидать доступа к базе данных SQL раньше, но не входит в вызов базы данных SQL. до истечения времени ожидания блокировки.
Также возможно, что серверная часть SQL является многопоточной, но ограничивает количество одновременных операций из-за уровня лицензирования. То есть демонстрационный движок SQL допускает только 2 одновременных запроса, но полностью лицензированный движок поддерживает десятки одновременных запросов.
В любом случае вам нужно что-то сделать, чтобы снизить уровень параллелизма до более приемлемых уровней. Предложение Джона Скита об использовании TaskScheduler для ограничения параллелизма звучит как хорошее место для начала.