it-swarm.com.ru

async Task <HttpResponseMessage> Get VS HttpResponseMessage Get

Мне нужна ваша помощь в следующем. В течение почти месяца я читал о задачах и асинхронности.

Я хотел попробовать реализовать мои новые знания в простом проекте wep api. У меня есть следующие методы, и оба они работают как положено:

 public HttpResponseMessage Get()
 {
        var data = _userServices.GetUsers();
        return Request.CreateResponse(HttpStatusCode.OK, data);
 }

public async Task<HttpResponseMessage> Get()
{
        var data = _userServices.GetUsers();


        return await Task<HttpResponseMessage>.Factory.StartNew(() =>
        {
           return Request.CreateResponse(HttpStatusCode.OK, data);
        });
 }

Итак, вопрос. Я попытался использовать Fiddler и посмотреть, в чем разница между этими двумя. Асинхронный немного быстрее, но кроме этого, какова реальная выгода от реализации чего-то подобного в веб-API?

7
Veronica_Zotali

Как уже отмечали другие, смысл async в ASP.NET заключается в том, что он освобождает один из потоков пула потоков ASP.NET. Это прекрасно работает для естественно асинхронных операций, таких как операции ввода-вывода, потому что это на один поток меньше на сервере (нет потока, который "обрабатывает" асинхронную операцию, как я объясню в моем блоге ). Таким образом, основным преимуществом async на стороне сервера является масштабируемость.

Однако вы хотите избежать Task.Run (и, что еще хуже, Task.Factory.StartNew) в ASP.NET. Я называю это «ложной асинхронностью», потому что они просто выполняют синхронную/блокирующую работу в потоке пула потоков. Они полезны в приложениях пользовательского интерфейса, где вы хотите, чтобы Push работал вне потока пользовательского интерфейса, чтобы пользовательский интерфейс оставался отзывчивым, но их (почти) никогда не следует использовать в ASP.NET или других серверных приложениях.

Использование Task.Run или Task.Factory.StartNew в ASP.NET фактически уменьшит вашу масштабируемость. Они вызовут некоторые ненужные переключатели потоков. Для более длительных операций вы можете отказаться от эвристики пула потоков ASP.NET, что приведет к ненужному созданию дополнительных потоков и их последующему уничтожению. Я исследую эти проблемы производительности шаг за шагом в другом сообщении в блоге .

Итак, вам нужно подумать о том, что делает каждое действие, и является ли любое из этих должно асинхронным. Если это так, то это действие должно быть асинхронным. В твоем случае:

public HttpResponseMessage Get()
{
  var data = _userServices.GetUsers();
  return Request.CreateResponse(HttpStatusCode.OK, data);
}

Что именно делает Request.CreateResponse? Это просто создание объекта ответа. Вот и все - просто фантазия new. Там нет ввода-вывода, и это, конечно, не то, что нужно отодвинуть в фоновый поток.

Тем не менее, GetUsers гораздо интереснее. Это больше похоже на чтение данных, основанное на is I/O. Если ваш бэкэнд может масштабироваться (например, Azure SQL/Таблицы/и т.д.), То вам следует сначала сделать это async, и как только ваша служба предоставит GetUsersAsync, тогда это действие также может стать async:

public async Task<HttpResponseMessage> Get()
{
  var data = await _userServices.GetUsersAsync();
  return Request.CreateResponse(HttpStatusCode.OK, data);
}
22
Stephen Cleary

Имеет больше смысла, когда вызов происходит с основными операциями IO . Да, Async работает быстрее, потому что он освобождает поток запросов на время выполнения операций. Таким образом, с точки зрения веб-сервера, вы возвращаете поток обратно в пул, который может использоваться сервером для любых будущих входящих вызовов.

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

Это хорошо для масштабируемости, которая включает в себя несколько серверов.

Так, например, когда SearchRecordAsync отправляет свой SQL в базу данных, он возвращает незавершенную задачу, а когда запрос достигает ожидающего, он возвращает поток запроса в пул потоков. Позже, когда операция БД завершается, поток запроса берется из пула потоков и используется для продолжения запроса.

Даже если вы не используете операции SQL, допустим, вы хотите отправить электронное письмо 10 людям. В этом случае также асинхронность имеет больше смысла. 

Async также очень удобен, чтобы показать прогресс длинного события. Таким образом, пользователь все равно получит активный графический интерфейс, пока задача выполняется в фоновом режиме.

Чтобы понять, пожалуйста, посмотрите на этот образец.

Здесь я пытаюсь инициировать задачу под названием отправка почты. Промежуточный период я ​​хочу обновить базу данных, пока фон выполняет задачу отправки почты.

После обновления базы данных она ожидает завершения задачи отправки почты. Тем не менее, при таком подходе становится совершенно ясно, что я могу запускать задачи в фоновом режиме и продолжать работу с оригинальным (основным) потоком.

using System;
using System.Threading;
using System.Threading.Tasks;

public class Program
{
    public static void Main()
    {
        Console.WriteLine("Starting Send Mail Async Task");
        Task task = new Task(SendMessage);
        task.Start();
        Console.WriteLine("Update Database");
        UpdateDatabase();

        while (true)
        {
            // dummy wait for background send mail.
            if (task.Status == TaskStatus.RanToCompletion)
            {
                break;
            }
        }

    }

    public static async void SendMessage()
    {
        // Calls to TaskOfTResult_MethodAsync
        Task<bool> returnedTaskTResult = MailSenderAsync();
        bool result = await returnedTaskTResult;

        if (result)
        {
            UpdateDatabase();
        }

        Console.WriteLine("Mail Sent!");
    }

    private static void UpdateDatabase()
    {
        for (var i = 1; i < 1000; i++) ;
        Console.WriteLine("Database Updated!");
    }

    private static async Task<bool> MailSenderAsync()
    {
        Console.WriteLine("Send Mail Start.");
        for (var i = 1; i < 1000000000; i++) ;
        return true;
    }
}
2
codebased

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

Тем не менее, использование Task.Factory.StartNew запускает другой поток так что вы вообще не получаете преимуществ масштабируемости. Ваш оригинальный поток может быть повторно использован, но вы перенесли работу в другой поток, так что нет никакой чистой выгоды вообще. на самом деле стоимость перехода на другой поток стоит, но это минимально.

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

1
Ned Stoyanov