Любая разница между «ждут Task.Run (); return; “и” return Task.Run () “?

Существует ли какая-либо концептуальная разница между следующими двумя частями кода:

async Task TestAsync() { await Task.Run(() => DoSomeWork()); } 

а также

 Task TestAsync() { return Task.Run(() => DoSomeWork()); } 

Сгенерированный код отличается?

EDIT: Чтобы избежать путаницы с Task.Run , аналогичный случай:

 async Task TestAsync() { await Task.Delay(1000); } 

а также

 Task TestAsync() { return Task.Delay(1000); } 

ПОСЛЕДНИЕ ОБНОВЛЕНИЯ: В дополнение к принятому ответу также существует разница в том, как обрабатывается LocalCallContext : CallContext.LogicalGetData восстанавливается даже там, где нет асинхронности. Зачем?

Обновлено , помимо различий в поведении распространения исключений, описанных ниже, есть еще несколько несколько тонких различий: версия async / await более подвержена блокировке блокировки в контексте синхронизации по умолчанию. Например, следующее будет заблокировано в приложении WinForms или WPF:

 static async Task TestAsync() { await Task.Delay(1000); } void Form_Load(object sender, EventArgs e) { TestAsync().Wait(); // dead-lock here } 

Измените его на не-асинхронную версию, и она не будет блокирована:

 Task TestAsync() { return Task.Delay(1000); } 

Степень мертвого замка хорошо объясняется Стивеном Клири в его блоге .


Другое важное различие заключается в распространении исключений. Исключение, созданное внутри метода async Task , сохраняется в возвращаемом объекте Task и остается бездействующим до тех пор, пока задача не будет обнаружена через task.Wait() await task , task.Wait() , task.Result или task.GetAwaiter().GetResult() . Он распространяется таким образом, даже если он выведен из синхронной части async метода.

Рассмотрим следующий код, где OneTestAsync и AnotherTestAsync ведут себя совершенно по-другому:

 static async Task OneTestAsync(int n) { await Task.Delay(n); } static Task AnotherTestAsync(int n) { return Task.Delay(n); } // call DoTestAsync with either OneTestAsync or AnotherTestAsync as whatTest static void DoTestAsync(Func whatTest, int n) { Task task = null; try { // start the task task = whatTest(n); // do some other stuff, // while the task is pending Console.Write("Press enter to continue"); Console.ReadLine(); task.Wait(); } catch (Exception ex) { Console.Write("Error: " + ex.Message); } } 

Если я вызову DoTestAsync(OneTestAsync, -2) , он DoTestAsync(OneTestAsync, -2) следующий результат:

 Нажмите enter для продолжения
 Ошибка: произошла одна или несколько ошибок.
 Ошибка: вторая

Заметьте, мне пришлось нажать Enter, чтобы увидеть его.

Теперь, если я вызову DoTestAsync(AnotherTestAsync, -2) , рабочий процесс кода внутри DoTestAsync совсем другой, и это DoTestAsync результат. На этот раз меня не просили нажать Enter :

 Ошибка: значение должно быть равно -1 (означает бесконечный тайм-аут), 0 или положительное целое число.
 Имя параметра: millisecondsDelayError: 1-й

В обоих случаях Task.Delay(-2) бросает в начале, Task.Delay(-2) его параметры. Это может быть сценарий со сценарием, но в теории Task.Delay(1000) тоже может бросить, например, когда сбой API-интерфейса базового таймера.

С другой стороны, логика распространения ошибок еще отличается для async void методов (в отличие от методов async Task ). Исключение, созданное внутри метода async void будет немедленно повторно выбрано в контексте синхронизации текущего streamа (через SynchronizationContext.Post ), если текущий stream имеет один ( SynchronizationContext.Current != null) . В противном случае он будет перезапущен через ThreadPool.QueueUserWorkItem ). У вызывающего абонента нет возможности обработать это исключение в одном стеке стека.

Я добавил несколько подробностей о поведении обработки исключений TPL здесь и здесь .


В : Возможно ли воспроизвести поведение распространения async для async методов на основе Task , чтобы последние не выбрасывали один и тот же стек стека?

A : Если это действительно необходимо, то да, есть трюк для этого:

 // async async Task MethodAsync(int arg) { if (arg < 0) throw new ArgumentException("arg"); // ... return 42 + arg; } // non-async Task MethodAsync(int arg) { var task = new Task(() => { if (arg < 0) throw new ArgumentException("arg"); // ... return 42 + arg; }); task.RunSynchronously(TaskScheduler.Default); return task; } 

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

В чем разница между

 async Task TestAsync() { await Task.Delay(1000); } 

а также

 Task TestAsync() { return Task.Delay(1000); } 

?

Я смущен этим вопросом. Позвольте мне попытаться уточнить, отвечая на ваш вопрос другим вопросом. В чем разница между?

 Func MakeFunction() { Func f = ()=>1; return ()=>f(); } 

а также

 Func MakeFunction() { return ()=>1; } 

?

Какая разница между двумя моими вещами, та же разница между вашими двумя вещами.

  1. Первый метод даже не компилируется.

    Поскольку « Program.TestAsync() » является асинхронным методом, который возвращает « Task », ключевое слово return не должно сопровождаться выражением объекта. Вы намеревались вернуть « Task »?

    Должно быть

     async Task TestAsync() { await Task.Run(() => DoSomeWork()); } 
  2. Существует большая концептуальная разница между этими двумя. Первый – asynchronous, второй – нет. Прочитайте Async Performance: Понимание затрат Async и Await, чтобы получить немного больше о внутреннем состоянии async / await .

  3. Они генерируют другой код.

     .method private hidebysig instance class [mscorlib]System.Threading.Tasks.Task TestAsync () cil managed { .custom instance void [mscorlib]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [mscorlib]System.Type) = ( 01 00 25 53 4f 54 65 73 74 50 72 6f 6a 65 63 74 2e 50 72 6f 67 72 61 6d 2b 3c 54 65 73 74 41 73 79 6e 63 3e 64 5f 5f 31 00 00 ) .custom instance void [mscorlib]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() = ( 01 00 00 00 ) // Method begins at RVA 0x216c // Code size 62 (0x3e) .maxstack 2 .locals init ( [0] valuetype SOTestProject.Program/'d__1', [1] class [mscorlib]System.Threading.Tasks.Task, [2] valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder ) IL_0000: ldloca.s 0 IL_0002: ldarg.0 IL_0003: stfld class SOTestProject.Program SOTestProject.Program/'d__1'::'<>4__this' IL_0008: ldloca.s 0 IL_000a: call valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::Create() IL_000f: stfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder SOTestProject.Program/'d__1'::'<>t__builder' IL_0014: ldloca.s 0 IL_0016: ldc.i4.m1 IL_0017: stfld int32 SOTestProject.Program/'d__1'::'<>1__state' IL_001c: ldloca.s 0 IL_001e: ldfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder SOTestProject.Program/'d__1'::'<>t__builder' IL_0023: stloc.2 IL_0024: ldloca.s 2 IL_0026: ldloca.s 0 IL_0028: call instance void [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::Startd__1'>(!!0&) IL_002d: ldloca.s 0 IL_002f: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder SOTestProject.Program/'d__1'::'<>t__builder' IL_0034: call instance class [mscorlib]System.Threading.Tasks.Task [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::get_Task() IL_0039: stloc.1 IL_003a: br.s IL_003c IL_003c: ldloc.1 IL_003d: ret } // end of method Program::TestAsync 

    а также

     .method private hidebysig instance class [mscorlib]System.Threading.Tasks.Task TestAsync2 () cil managed { // Method begins at RVA 0x21d8 // Code size 23 (0x17) .maxstack 2 .locals init ( [0] class [mscorlib]System.Threading.Tasks.Task CS$1$0000 ) IL_0000: nop IL_0001: ldarg.0 IL_0002: ldftn instance class [mscorlib]System.Threading.Tasks.Task SOTestProject.Program::'b__4'() IL_0008: newobj instance void class [mscorlib]System.Func`1::.ctor(object, native int) IL_000d: call class [mscorlib]System.Threading.Tasks.Task [mscorlib]System.Threading.Tasks.Task::Run(class [mscorlib]System.Func`1) IL_0012: stloc.0 IL_0013: br.s IL_0015 IL_0015: ldloc.0 IL_0016: ret } // end of method Program::TestAsync2 

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

Напротив, когда метод не отмечен async вы теряете способность await ожиданий. (То есть, внутри самого метода, этот метод все еще может ожидать его вызывающий.) Однако, избегая ключевого слова async , вы больше не генерируете государственную машину, которая может добавить справедливый бит накладных расходов (линять местных жителей на поля государственной машины, дополнительные объекты для GC).

В примерах, подобных этому, если вы можете избежать async-await и вернуться к ожиданию напрямую, это должно быть сделано для повышения эффективности метода.

См. Этот вопрос и этот ответ, который очень похож на ваш вопрос и этот ответ.

  • Каков самый простой способ зашифровать пароль, когда я сохраняю его в реестре?
  • Как использовать C # для дезинфекции ввода на странице html?
  • Аргументы шаблона по умолчанию для шаблонов функций
  • запустить программу с несколькими исходными файлами в компиляторе GNU c ++
  • C # Begin / EndReceive - как читать большие данные?
  • Как напечатать тип int64_t в C
  • C ++: безопасно ли указывать указатель на int и позже на указатель?
  • Как выпустить указатель из boost :: shared_ptr?
  • Лучший способ получить ссылку на главную страницу MVC 3 Razor
  • Самый быстрый способ взаимодействия между живыми (несохраненными) данными Excel и объектами C #
  • Ядро обнуляет память?
  • Давайте будем гением компьютера.