2016-11-10 1 views
2

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

Например, SqlCommand имеет каждый метод в сочетании с его версией async. В этом случае у нас есть оператор insert, поэтому мы можем позвонить ExecuteNonQuery или ExecuteNonQueryAsync.

Часто в инструкциях async/wait мы читаем, что если у вас есть асинхронная версия, доступная для какого-либо метода, вы должны ее использовать. Предположим, что мы пишем:

async Task Save(IEnumerable<Savable> savables) 
{ 
    foreach(var savable in savables) 
    { 
     //create SqlCommand somehow 
     var sqlCmd = CreateSqlCommand(savable); 

     //use asynchronous version 
     await sqlCmd.ExecuteNonQueryAsync(); 
    } 
} 

Этот код очень прост. Тем не менее, каждый раз, когда он выходит из части ожидания, он также возвращается в поток пользовательского интерфейса, а затем обратно в фоновый поток в следующем ожидании и т. Д. (Не так ли?). Это означает, что пользователь может увидеть некоторое отставание, поскольку поток пользовательского интерфейса непрерывно прерывается продолжением await для выполнения следующего цикла foreach, и за это время пользовательский интерфейс немного замерзает.

Я хочу знать, если я лучше писать код так:

async Task Save(IEnumerable<Savable> savables) 
{ 
    await Task.Run(() => 
    { 
     foreach(var savable in savables) 
     { 
      //create SqlCommand somehow 
      var sqlCmd = CreateSqlCommand(savable); 

      //use synchronous version 
      sqlCmd.ExecuteNonQuery(); 
     } 
    }); 
} 

Таким образом, вся foreach выполняется на вторичном потоке, без непрерывного переключения между нитью UI и второстепенному. Это подразумевает, что поток пользовательского интерфейса может обновлять View в течение всего времени foreach (например, счетчик или индикатор выполнения), то есть пользовательский лаг не воспринимается.

Я прав? или мне что-то не хватает о «асинхронном обратном пути»?

Я не ищу простые ответы на основе мнений. Я ищу объяснение инструкций async/await в случае, если это и для наилучшего способа его решения.

EDIT:

Я прочитал this question, но это не то же самое. Это вопрос о выборе SINGLE await по асинхронному методу в сравнении с одним Task.Run. Этот вопрос связан с последствиями вызова 1000 await и накладными расходами ресурсов из-за непрерывного переключения между потоками.

+0

Возможный дубликат [Вызов метода асинхронизации с использованием Task.Run кажется неправильным?] (Http://stackoverflow.com/questions/32606485/calling-an-async-method-using-a-task-run-seems -wrong) – Liam

+2

Что вам не хватает, так это то, что Threads чрезвычайно дороги, поэтому вы действительно хотите избежать их создания, если сможете. – Aron

+0

Метод 'Task.Run' - это * синхронизация по асинхронному шаблону *. Не делайте этого – Liam

ответ

5

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

Что вам не хватает, конечно, является предпочтительным способом избежать всех обратных вызовов в поток пользовательского интерфейса. Когда вы выполните await операцию, если вам действительно не нужен остальная часть метода, чтобы вернуться в исходный контекст, вы можете просто добавить ConfigureAwait(false) в конец ожидаемой задачи. Это предотвратит запуск продолжения в текущем контексте (который является потоком пользовательского интерфейса) и вместо этого продолжит работу в потоке пула потоков.

Использование ConfigureAwait(false) позволяет избежать пользовательского интерфейса, который ответственен за работу без использования интерфейса, а также не позволяет планировать потоки потоков потоков, чтобы выполнять больше работы, чем нужно.

Конечно, если работа, которую вы в конечном итоге делает после того, как ваше продолжение на самом деле происходит, чтобы сделать работу пользовательского интерфейса, то этот метод не следует использовать ConfigureAwait(false);, потому что он на самом деле хочет планировать продолжение в потоке пользовательского интерфейса ,

+0

Хорошо, у меня есть выбор, чтобы использовать 'ConfigureAwait', и с точки зрения результата, теперь у меня есть 2 вполне одинаковых варианта. Теперь почему я должен использовать 'ConfigureAwait' вместо' Task.Run'? Это только вопрос читаемости? –

+1

@MassimilianoKraus Это означает, что продолжения после запуска операции async будут выполняться в пуле потоков, вместо того, чтобы пул потоков * запускать * асинхронные действия (а также обрабатывать все продолжения). Использование 'ConfigureAwait' также позволяет вам использовать все методы, которые * не нужно * запускать в потоке пользовательского интерфейса, использовать пул потоков, позволяя всем, что нужно использовать интерфейс, просто не добавлять' ConfigureAwait (false) 'и иметь возможность взаимодействовать с пользовательским интерфейсом. – Servy

0

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

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

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

Если вы идете по маршруту событий, вы можете обновить пользовательский интерфейс асинхронно с результатами, как они приходят в

-2

Существует одно важное различие между двумя решениями. Первый из них однопоточный (если вы не используете ConfigureAwait(false), как предложено @Servy), а второй - многопоточным.

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

+0

На самом деле вы не знаете, есть ли в вашей библиотеке 'Task.Run' внутри или нет. Вы можете описать ситуацию в комментарии «нет потока» (см. Вопрос), или вы можете иметь библиотеку, которая предоставляет метод «async», который внутри имеет «Task.Run». В последнем случае поток IS многопоточен, даже если извне вы используете только «ожидание». –

+0

@MassimilianoKraus Согласен, но я думаю, что не хватает смысла. Любая библиотека, которую вы вызываете, может иметь скрытый многопоточный код внутри него. Я специально говорю о введении многопоточности с кодом, который вы хотите написать. – MEMark

Смежные вопросы