Использование async/wait или task в контроллере web api (ядро .net)
У меня есть .net core API, у которого есть контроллер, который строит агрегированный объект для возврата.
создаваемый объект создается из данных, поступающих из трех вызовов метода в класс службы. Все они независимы друг от друга и могут выполняться изолированно друг от друга.
В настоящее время я использую задачи для повышения производительности этого контроллера. текущая версия выглядит примерно так:
[HttpGet]
public IActionResult myControllerAction()
{
var data1 = new sometype1();
var data2 = new sometype2();
var data3 = new List<sometype3>();
var t1 = new Task(() => { data1 = service.getdata1(); });
t1.Start();
var t2 = new Task(() => { data2 = service.getdata2(); });
t2.Start();
var t3 = new Task(() => { data3 = service.getdata2(); });
t3.Start();
Task.WaitAll(t1, t2, t3);
var data = new returnObject
{
d1 = data1,
d2 = data2,
d2 = data3
};
return Ok(data);
}
Это хорошо работает, но мне интересно, является ли использование задач лучшим решением здесь? Будет ли использование async/await лучшей идеей и более приемлемым способом?
Например, если контроллер должен быть помечен как асинхронный и ждать каждого вызова методам службы?
Ответы
Ответ 1
Это хорошо работает, но мне интересно, является ли использование задач лучшим решением здесь? Будет ли использование async/await лучшей идеей и более приемлемым способом?
Да, абсолютно. Выполнение параллельной обработки в ASP.NET потребляет несколько потоков для каждого запроса, что может серьезно повлиять на вашу масштабируемость. Асинхронная обработка намного выше для ввода-вывода.
Чтобы использовать async
, сначала начните с вашего вызова самого низкого уровня, где-нибудь внутри вашего сервиса. Вероятно, он делает HTTP-вызов в какой-то момент; измените это на использование асинхронных HTTP-вызовов (например, HttpClient
). Тогда пусть async
естественно растет оттуда.
В итоге вы получите асинхронные методы getdata1Async
, getdata2Async
и getdata3Async
, которые могут быть использованы одновременно:
[HttpGet]
public async Task<IActionResult> myControllerAction()
{
var t1 = service.getdata1Async();
var t2 = service.getdata2Async();
var t3 = service.getdata3Async();
await Task.WhenAll(t1, t2, t3);
var data = new returnObject
{
d1 = await t1,
d2 = await t2,
d3 = await t3
};
return Ok(data);
}
При таком подходе, когда выполняются три вызова службы, myControllerAction
использует ноль, а не четыре.
Ответ 2
[HttpGet]
public async Task<IActionResult> GetAsync()
{
var t1 = Task.Run(() => service.getdata1());
var t2 = Task.Run(() => service.getdata2());
var t3 = Task.Run(() => service.getdata3());
await Task.WhenAll(t1, t2, t3);
var data = new returnObject
{
d1 = t1.Status == TaskStatus.RanToCompletion ? t1.Result : null,
d2 = t2.Status == TaskStatus.RanToCompletion ? t2.Result : null,
d3 = t3.Status == TaskStatus.RanToCompletion ? t3.Result : null
};
return Ok(data);
}
- В настоящее время поток действий заблокирован, когда вы ждете задач. Используйте
TaskWhenAll
, чтобы вернуть ожидаемый объект Task. Таким образом, с помощью метода async вы можете ждать задач вместо блокировки потока.
- Вместо создания локальных переменных и назначения их в задачах вы можете использовать
Task<T>
для возврата результатов требуемого типа.
- Вместо создания и запуска задач используйте метод
Task<TResult>.Run
- Я рекомендую использовать соглашение для имен действий - если действие принимает запрос GET, оно должно начинаться с
Get
- Затем вы должны проверить, успешно ли выполнены задачи. Это делается путем проверки состояния задачи. В моем примере я использовал значения
null
для свойств возвращаемого объекта, если некоторые из задач не завершены успешно. Вы можете использовать другой подход - например, return, если некоторые задачи не удались.
Ответ 3
Как я понимаю, вы хотите, чтобы это выполнялось параллельно, поэтому я не думаю, что с вашим кодом что-то не так. Как сказал Габриэль, вы можете дождаться завершения задач.
[HttpGet]
public async IActionResult myControllerAction()
{
var data1 = new sometype1();
var data2 = new sometype2();
var data3 = new List<sometype3>();
var t1 = Task.Run(() => { data1 = service.getdata1(); });
var t2 = Task.Run(() => { data2 = service.getdata2(); });
var t3 = Task.Run(() => { data3 = service.getdata3(); });
await Task.WhenAll(t1, t2, t3); // otherwise a thread will be blocked here
var data = new returnObject
{
d1 = data1,
d2 = data2,
d2 = data3
};
return Ok(data);
}
Вы также можете использовать результаты задач для сохранения некоторых строк кодов и сделать код "лучше" (см. комментарии):
[HttpGet]
public async IActionResult myControllerAction()
{
var t1 = Task.Run(() => service.getdata1() );
var t2 = Task.Run(() => service.getdata2() );
var t3 = Task.Run(() => service.getdata3() );
await Task.WhenAll(t1, t2, t3); // otherwise a thread will be blocked here
var data = new returnObject
{
d1 = t1.Result,
d2 = t2.Result,
d2 = t3.Result
};
return Ok(data);
}