Лучшая производительность с использованием асинхронных и ожидающих
У меня есть одноядерный компьютер, и я хотел бы улучшить производительность своего кода, используя async и await. Код состоит из двух основных частей. первая операция ввода-вывода. (чтение из очереди служебной шины Azure для нескольких sessionId), второе, обработка данных - загрузка процессора. Я обернул метод DequeueAsync в асинхронный метод, который возвращает задачу:
private async Task<SomeReturnValue> AcceptFromQueueAsync(){
try{
SomeReturnValue result = await DequeueAsync.configureAwait(false);
return SomeReturnValue;
}
catch{
//logging and stuff
}
Тяжелым методом для CPU является sync: DoSyncWork () Поскольку вторая часть является тяжелой для CPU, я не хочу использовать параллелизм (на самом деле не могу...). ЦП может начинаться только после завершения IO-части. Учитывая вышеизложенное, является ли следующая реализация способом использования 1 процессора?
private void AcceptAndProcessWrapper(){
//Count is some cosnt defined outside the method
_acceptTasks = new List<Task<SomeReturnValue>>();
for (var i = 0; i < Count; i++)
{
_acceptTasks.Add(AcceptFromQueueAsync());
}
//The use of list is for convince in processing
Task.WaitAll(_acceptTasks.ToArray());
//The CPU part
Task.Run(DoSyncWork).Wait();
}
Я знаю, что мог бы не использовать Task.Run() для части синхронизации, но я хочу разрешить реализацию функций на нескольких ядрах (запустив несколько задач с помощью Task.Run() и удерживая их в массиве) Будет ли приведенная выше реализация, (Несколько вызовов асинхронного метода, который возвращает задачу) улучшить производительность? Или я должен начать новую задачу для каждого асинхронного вызова, например Task.Run(AcceptFromQueueAsync)?
4 ответа
Код, который у вас есть, действительно выполняет параллельное асинхронное удаление очереди. Он запустит все асинхронные задачи сразу, а затем подождет, пока все они будут выполнены, чтобы продолжить.
Добавление дополнительного звонка Task.Run
не поможет Запуск асинхронной задачи в другом потоке - это просто добавление дополнительных издержек без добавленной стоимости. Task.Run
следует использовать для запуска связанной с ЦП работы в другом потоке, а не асинхронной работы в другом потоке.
Говоря о производительности с заданием (ями) (если вы будете использовать его в будущем с DoSyncWork
Вы должны также иметь в виду, что Task.Run
по умолчанию использует ThreadPool, но ожидается, что потоки из пула потоков будут выполнять небольшую и быструю работу. Если задачи действительно являются "тяжелыми", вам следует рассмотреть возможность их запуска во вновь созданных потоках, что также может быть вызвано следующим:
new Task(() => { }, TaskCreationOptions.LongRunning);
РЕДАКТИРОВАТЬ:
После некоторого разговора в комментариях ниже, я бы хотел немного прояснить ситуацию.
В настоящее время, Task.Run(DoSyncWork).Wait();
на самом деле не имеет никакого смысла (даже больше, это может занять дополнительное время для запуска задачи в другом потоке (даже из потоков ThreadPool), но если вы хотите использовать несколько ядер в будущем, вам, очевидно, следует переместить эту задачу в другой поток, чтобы запустите (и если он действительно сильно загружен процессором, подумайте о запуске задачи как " LongRunning ") (если это, конечно, необходимо для вашего приложения (не знаю, какой у вас тип приложения)). В этом случае все еще нет нужно позвонить .Wait()
там мгновенно.
Выполнение первой части асинхронно может иметь смысл, в зависимости от того, в имени есть Async, что говорит о том, что он не блокируется в любом случае, в этом случае нет необходимости, но определенно нет необходимости запускать DoSyncWork асинхронно, так как его всего 1 процесс, и вы ждем его до конца.
Я использую шаблон, похожий на этот, чтобы попытаться максимально увеличить скорость ввода-вывода и процессора
public async void DoWork()
{
Task cpuBoundTask = null;
Task ioBoundTask = null;
while(true)
{
ioBoundTask = DoIOBoundStuff();
var ioResult = await ioBoundTask;
if(cpuBoundTask != null)
{
await cpuBoundTask;
}
cpuBoundTask = DoCpuBoundStuff(ioResult);
}
}