Есть ли концептуальная разница между следующими двумя частями кода:
async Task TestAsync()
{
await Task.Run(() => DoSomeWork());
}
а также
Task TestAsync()
{
return Task.Run(() => DoSomeWork());
}
Сгенерированный код тоже отличается?
РЕДАКТИРОВАТЬ: Чтобы избежать путаницы с Task.Run
аналогичным случаем:
async Task TestAsync()
{
await Task.Delay(1000);
}
а также
Task TestAsync()
{
return Task.Delay(1000);
}
ПОСЛЕДНЕЕ ОБНОВЛЕНИЕ: помимо принятого ответа, также есть разница в том, как LocalCallContext
обрабатывается: CallContext.LogicalGetData восстанавливается даже там, где нет асинхронности. Зачем?
c#
async-await
избегать
источник
источник
await
/async
вообще :)Ответы:
Одно из основных различий заключается в распространении исключений. Исключение, брошенное внутри
async Task
методы, сохраняется в возвращенномTask
объекте и остается бездействующим , пока задача не получает наблюдаются через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<int, Task> 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)
, он выдаст следующий результат:Обратите внимание, мне пришлось нажать, Enterчтобы увидеть это.
Теперь, если я позвоню
DoTestAsync(AnotherTestAsync, -2)
, рабочий процесс кода внутриDoTestAsync
будет совсем другим, как и результат. На этот раз меня не просили нажимать Enter:В обоих случаях
Task.Delay(-2)
бросает в начале, проверяя его параметры. Это может быть выдуманный сценарий, но теоретически он такжеTask.Delay(1000)
может сработать, например, при отказе базового API системного таймера.Кстати, логика распространения ошибок отличается для
async void
методов (в отличие отasync Task
методов). Исключение, возникшее внутриasync void
метода, будет немедленно повторно выбрано в контексте синхронизации текущего потока (черезSynchronizationContext.Post
), если у текущего потока он есть (SynchronizationContext.Current != null)
. В противном случае оно будет повторно выбрано черезThreadPool.QueueUserWorkItem
). У вызывающей стороны нет возможности обработать это исключение в том же кадре стека.Я разместил более подробную информацию о поведении обработки исключений TPL здесь и здесь .
В : Можно ли имитировать поведение распространения исключений для
async
методов, неTask
основанных на асинхронном режиме , чтобы последние не создавали один и тот же кадр стека?О : Если действительно нужно, то да, для этого есть трюк:
// async async Task<int> MethodAsync(int arg) { if (arg < 0) throw new ArgumentException("arg"); // ... return 42 + arg; } // non-async Task<int> MethodAsync(int arg) { var task = new Task<int>(() => { if (arg < 0) throw new ArgumentException("arg"); // ... return 42 + arg; }); task.RunSynchronously(TaskScheduler.Default); return task; }
Однако обратите внимание, что при определенных условиях (например, когда он находится слишком глубоко в стеке)
RunSynchronously
все еще может выполняться асинхронно.Еще одно заметное отличие заключается в том, что
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); }
Природу тупика хорошо объяснил Стивен Клири в своем блоге .
источник
return Task.Run()
иawait Task.Run(); return
, а неawait Task.Run().ConfigureAwait(false); return
Меня смущает этот вопрос. Позвольте мне прояснить ситуацию, ответив на ваш вопрос другим вопросом. В чем разница между?
Func<int> MakeFunction() { Func<int> f = ()=>1; return ()=>f(); }
а также
Func<int> MakeFunction() { return ()=>1; }
?
Какой бы ни была разница между моими двумя вещами, такая же разница есть между вашими двумя вещами.
источник
Task.Delay(1000).ContinueWith(() = {})
. Во втором это простоTask.Delay(1000)
. Разница несколько тонкая, но существенная.Первый метод даже не компилируется.
Должно быть
async Task TestAsync() { await Task.Run(() => DoSomeWork()); }
Между этими двумя понятиями существует большая концептуальная разница. Первый асинхронный, второй - нет. Прочтите Async Performance: Understanding the Costs of Async and Await, чтобы узнать больше о внутреннем устройстве
async
/await
.Они генерируют другой код.
.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/'<TestAsync>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/'<TestAsync>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/'<TestAsync>d__1'::'<>t__builder' IL_0014: ldloca.s 0 IL_0016: ldc.i4.m1 IL_0017: stfld int32 SOTestProject.Program/'<TestAsync>d__1'::'<>1__state' IL_001c: ldloca.s 0 IL_001e: ldfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder SOTestProject.Program/'<TestAsync>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::Start<valuetype SOTestProject.Program/'<TestAsync>d__1'>(!!0&) IL_002d: ldloca.s 0 IL_002f: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder SOTestProject.Program/'<TestAsync>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::'<TestAsync2>b__4'() IL_0008: newobj instance void class [mscorlib]System.Func`1<class [mscorlib]System.Threading.Tasks.Task>::.ctor(object, native int) IL_000d: call class [mscorlib]System.Threading.Tasks.Task [mscorlib]System.Threading.Tasks.Task::Run(class [mscorlib]System.Func`1<class [mscorlib]System.Threading.Tasks.Task>) 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
и напрямую вернуть ожидаемый объект, это должно быть сделано для повышения эффективности метода.См. Этот вопрос и этот ответ, которые очень похожи на ваш вопрос и этот ответ.
источник