Как обрабатывать асин c метод, который имеет некоторые ожидающие и некоторые связанные с процессором операции - PullRequest
4 голосов
/ 27 марта 2020

Мне все еще не удается обернуть голову асинхронным ожиданием. Я пытаюсь выяснить, нужно ли мне обернуть свою интенсивную загрузку ЦП в Task.Run ниже. Может кто-нибудь сказать мне, что лучший способ сделать это?

Без Task.Run:

public async Task ProcessData()
{
    //Get some data
    var httpClient = new HttpClient();
    var response = await httpClient.GetAsync("myUrl");
    string data = await response.Content.ReadAsStringAsync();

    //Calculate result
    string result = null;
    for (int i = 0; i < 10000000; i++)
    {
        //Do some CPU intensive work
    }

    //Save results
    using (var fs = new FileStream("myFile.txt", FileMode.Create))
    using (var writer = new StreamWriter(fs))
    {
        await writer.WriteLineAsync(result);
    }
}

С Task.Run:

public async Task ProcessData()
{
    //Get some data
    var httpClient = new HttpClient();
    var response = await httpClient.GetAsync("myUrl");
    string data = await response.Content.ReadAsStringAsync();

    //Calculate result
    string result = null;
    await Task.Run(() =>
    {
        for (int i = 0; i < 10000000; i++)
        {
            //Do some CPU intensive work
        }
    });

    //Save results
    using (var fs = new FileStream("myFile.txt", FileMode.Create))
    using (var writer = new StreamWriter(fs))
    {
        await writer.WriteLineAsync(result);
    }
}

Мой инстинкт состоит в том, чтобы не использовать Task.Run, но в таком случае это будет означать, что весь код в асинхронном c методе будет выполняться в отдельном потоке? То есть вызывающий поток не будет прерван до самого конца (поток файлов закрыт и метод завершается)?

Или поток вызывающей стороны будет прерван и go вернется к этому методу, чтобы сначала выполнить интенсивную ЦП, а затем go к тому, что он делал, пока ProcessData () работает в фоновом режиме для FileStream часть?

Надеюсь, это понятно.

Заранее спасибо

Ответы [ 4 ]

5 голосов
/ 27 марта 2020

Как описано в моем asyn c intro , метод async начинает выполняться так же, как и любой другой метод, непосредственно в стеке вызывающего потока. Когда await действует асинхронно, он захватывает «контекст», «приостанавливает» метод и возвращает его вызывающей стороне. Позже, когда «асинхронное ожидание» (await) будет завершено, метод будет возобновлен в этом захваченном контексте.

Итак, это означает:

это будет означать, что весь код в методе asyn c будет выполняться в отдельном потоке?

Нет отдельного потока . Никакой поток вообще не «выполняет» await, потому что ничего не нужно делать. Когда await завершается, метод продолжает выполняться в этом захваченном контексте. Иногда это означает, что метод помещается в очередь сообщений, где он в конечном итоге будет запущен исходным вызывающим потоком (например, приложения пользовательского интерфейса работают таким образом). В других случаях это означает, что метод выполняется любым доступным потоком пула потоков (большинство других приложений работают таким образом).

Это означает, что вызывающий поток не будет прерван до самого конца (Поток файлов закрыт а метод заканчивается)? Или поток вызывающих будет прерван и go вернется к этому методу, чтобы сначала выполнить интенсивную ЦП часть, а затем go вернуться к тому, что он делал, пока ProcessData () работает в фоновом режиме для части FileStream?

Вызывающий поток никогда не прерывается.

Часть работы, связанная с ЦП, будет выполняться в любом «контексте», захваченном в точке await. Это может быть исходный поток или поток пула потоков, в зависимости от контекста.

Итак, вернемся к первоначальному вопросу:

Может кто-нибудь сказать мне, что лучший способ сделать это?

Если это код, который можно вызывать из разных контекстов, то я рекомендую не использовать Task.Run, но я рекомендую документировать, что метод выполняет привязку к процессору Работа. Таким образом, если вызывающая сторона вызывает этот метод из потока пользовательского интерфейса, то она знает, что при вызове его следует использовать Task.Run, чтобы гарантировать, что поток пользовательского интерфейса не заблокирован.

3 голосов
/ 27 марта 2020

если мне нужно обернуть свою интенсивную загрузку ЦП в Task.Run

Когда вы пишете приложение для рабочего стола (WinForms, WPF), тогда Да, используйте Task.Run () для работы с интенсивным использованием процессора.

Когда вы пишете asp. net Серверное приложение, тогда Нет, вам не нужно больше потоков. Используйте первый пример.

Для кода вызова это не имеет большого значения. Вызывающий метод не «прерывается», он (а) ожидает.

Метод asyn c не «запускается в потоке», после каждого ожидания его можно продолжить в другом потоке.

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

2 голосов
/ 27 марта 2020

Используйте async для выполнения работы с привязкой к ЦП по причинам реагирования .

Почему здесь помогает asyn c?

asyn c и await - это лучшая практика для управления работой с процессором, когда вам нужна отзывчивость.

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

Например, обычно вы хотите избежать работы с процессором в потоке пользовательского интерфейса или в контексте синхронизации пользовательского интерфейса, поскольку в результате вы можете получить замороженный файл. / неотвечающий интерфейс. В этом случае вы можете использовать Task.Run для планирования работы для следующего доступного потока в пуле потоков.

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

Например, вы хотите выполнить вычисление и, ожидая результата, вы Вы хотите вызвать удаленный API для чего-то другого, чтобы вы могли объединить результаты вместе, когда они оба вернутся.

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

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

1 голос
/ 27 марта 2020

Может быть элегантное решение без Task.Run, если вы можете принять некоторые ограничения.

...
//Get some data
var httpClient = new HttpClient();
var response = await httpClient.GetAsync("myUrl").ConfigureAwait(false);
string data = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

//Calculate result
...

Ваш связанный с процессором код будет выполняться в некотором потоке пула потоков, если хотя бы один асинхронный c метод до этого действительно работал асинхронно. Если оба завершаются синхронно, следующий код будет выполняться в вызывающем потоке. Это может быть проблемой, а может и нет. (ie если асинхронный код c естественным образом завершается синхронно, это может быть связано с какой-то ошибкой. Тогда может случиться так, что вам вообще не потребуется вызывать код, интенсивно использующий процессор.)

Второе ограничение - вы теряете исходный контекст синхронизации, и await ProcessData() может продолжаться не в вызывающем потоке.

Подробнее о ConfigureAwait можно прочитать здесь .

...