Являются ли ресурсы потока задач .NET возвращенными обратно в пул временно, если поток ожидает завершения операции async?

У меня есть задача TPL, которая делает две вещи. Во-первых, он вызывает веб-службу. Во-вторых, он вставляет некоторые данные в базу данных. У меня есть до 20 задач, которые запускались в одно и то же время снова и снова. Все, что они делают весь день, - это веб-службы вызова и вставка данных в базу данных.

Я новичок в TPL в .NET. Я сделал некоторые вещи с фоновыми рабочими процессами и асинхронными веб-службами.

Вызов веб-службы и вставка базы данных блокируют вызовы в потоке, в котором запущена задача.

Я понимаю, что под обложками, когда вы используете Tasks,.NET управляет пулом потоков для вас. Да?

Будет ли пул потоков иметь больше потоков в его распоряжении, если я сделал вызов службы и вызов базы данных с помощью async и wait() вместо того, чтобы блокировать вызовы?

Моя теория (и я не уверен, почему я так думаю) заключается в том, что поток занят тем, что ничего не делает, ожидая блокирующей веб-службы и не может временно вернуть свои ресурсы в пул. Но я задаюсь вопросом, ждут ли задачи для асинхронных вызовов, чтобы завершить то, сможет ли основной поток задач переключиться, чтобы другие процессы обрабатывались во время ожидания.

Является ли моя теория правильной? Или я делаю вещи?

Я использую С# и .NET 4.0, но при необходимости я мог бы перейти к 4.5.

Ответ 1

Будет ли пул потоков иметь больше потоков в его распоряжении, если я сделаю вызов службы и вызов базы данных с помощью async и wait() вместо заставляя их блокировать вызовы?

Это зависит от того, что вы подразумеваете под "использованием async-ожидания".

Когда вы используете Task.Run, за кулисами, класс Task использует ThreadPool для разгрузки, используя поток ThreadPool.

Если ваша служба не раскрывает истинный async api, и вы используете Task.Run для очереди вашей работы, вы все равно будете блокировать поток threadpool для работы с IO-связью, независимо от использования async-await. В вашем вопросе вы заявляете, что оба вызова блокируют вызовы, и в этом случае ответ отрицательный, поток threadpool, используемый для блокировки вызовов блокировки, все равно блокируется.

Если ваши вызовы службы и базы данных были истинными асинхронными API (которые не потребляют лишние потоки для выполнения своей работы), вы можете использовать async-await, например, когда вы await на одном из этих вызовов (и вам не нужно будет использовать Task.Run с ними вообще), текущий поток даст управление обратно вызывающему абоненту и может использоваться в то же время для выполнения большей работы. Если это так, то да.

Моя теория (и я не уверен, почему я так думаю) заключается в том, что поток занят тем, что ничего не делает, ожидая блокирующей веб-службы и не может временно вернуть свои ресурсы в пул. Но я задаюсь вопросом, ждут ли задачи для асинхронных вызовов, чтобы завершить то, сможет ли основной поток задач переключиться, чтобы другие процессы обрабатывались во время ожидания.

Ваша теория верна. Если основное задание работы в очереди по потоку - это сделать запрос с привязкой к IO, тогда его расходы в большинстве случаев просто блокируются до тех пор, пока запрос не завершится.

Когда вы await a Task, управление возвращается к вызывающему. Предположим, что ваш служебный вызов был вызовом REST, вы можете использовать HttpClient, который предоставляет истинные неинтерпретирующие асинхронные методы, такие как GetAsync, PostAsync, а когда вы await эти вызовы, ваш вызывающий поток будет выпущен на сделайте еще большую работу.

Ответ 2

Если все блоки приложений задаются, каждая задача будет использовать поток из пула потоков.

Если все задачи регулярно await, пулу потоков не нужно использовать нить для каждой задачи.

Когда ваш код await еще не завершил операцию, состояние метода сохраняется таким образом, что оно может быть возобновлено в любом другом потоке.

Простые потоки threadpool освобождаются через некоторое время, поэтому фактический поток, который попадает в await, может быть освобожден из пула потоков, пока метод, вызывающий await, все еще работает.

Объединяя все это, асинхронная версия подпрограммы может выполнять ту же работу с меньшим количеством потоков (при условии, что рабочая нагрузка имеет достаточный баланс времени, ожидающий против вращения процессора).

Этот код запускает 100 задач, выполняющих синхронное ожидание:

var numTasks = 100;
for (int i = 0; i < numTasks; i++)
{
    Thread.Sleep(5);
    Task.Run(() =>
    {
        Thread.Sleep(5000);
        Interlocked.Decrement(ref numTasks);
    });
}
while (numTasks > 0) Thread.Sleep(100);

Для асинхронного ожидания измените его на:

    Task.Run(async () =>
    {
        await Task.Delay(5000);
        Interlocked.Decrement(ref numTasks);
    });

В моей системе версия async увеличивает количество пиковых потоков вдвое меньше, и занимает 20% времени для выполнения одной и той же "работы".

Ответ 3

Ответ: да. Хотя технически это не "ожидание" для завершения операции async (в противном случае для async не было бы никакой пользы). Под капотом есть делегат обратного вызова, который запускается, когда операция async завершается, что позволяет вашему вызывающему потоку продолжать работу без блокировки. Это асинхронная/ждущая магия, которая превращает эти "продолжения" в линейно выглядящую часть кода.

Поскольку вы используете поток threadpool, когда он достигает ожидания, поток возвращается в threadpool. Здесь нужно быть осторожным, так как нормальное поведение - это когда ожидаемая операция завершается, он попытается вернуться к потоку, на котором он был запущен (теперь, вероятно, используется другой задачей), поэтому вы можете наблюдать проблемы с задержкой при получении результаты назад, поскольку потоки threadpool теперь связаны с другими задачами. Со временем поток будет пытаться настроить количество доступных потоков для удовлетворения спроса, но вы можете обнаружить, что это не происходит достаточно быстро, если вы работаете, поступает в пакеты. Результат будет, по-видимому, плохим, поскольку у вас может быть только небольшое количество доступных потоков.