Любая разница между " ожидание задачи.Выполнить(); возврат;" и "обратная задача.Выполнить()"?
есть ли концептуальная разница между следующими двумя частями кода:
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 получает восстанавливается даже там, где нет асинхронности. Зачем?
4 ответа:
Обновлено, помимо различий в поведении распространения исключений, описанных ниже, есть еще одно несколько тонкое различие:
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); }
природа dead-lock хорошо объясняется Стивен Клири в своем блог.
Еще одно важное отличие заключается в распространение исключения. исключение, брошенное внутри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)
, он производит следующий вывод:Press enter to continue Error: One or more errors occurred.await Task.Delay Error: 2ndобратите внимание, я должен был нажать введите чтобы увидеть его.
теперь, если я называю
DoTestAsync(AnotherTestAsync, -2)
рабочий процесс внутриDoTestAsync
совсем другое, и таков выход. На этот раз меня не попросили нажать введите:Error: The value needs to be either -1 (signifying an infinite timeout), 0 or a positive integer. Parameter name: millisecondsDelayError: 1stв обоих случаях
Task.Delay(-2)
бросает в начале, пока проверка его параметров. Это может быть выдуманный сценарий, но в теорииTask.Delay(1000)
может бросить тоже, например, когда основной системный таймер API терпит неудачу.на стороне Примечание, логика распространения ошибок еще отличается для
async void
методы (вместоasync Task
методов). Исключение, возникшее внутриasync void
метод будет немедленно повторно брошен в контекст синхронизации текущего потока (черезSynchronizationContext.Post
), если текущий поток один (SynchronizationContext.Current != null)
. В противном случае, он будет повторно брошен черезThreadPool.QueueUserWorkItem
). У вызывающего объекта нет возможности обработать это исключение в том же кадре стека.я опубликовал еще несколько подробностей о поведении обработки исключений TPL здесь и здесь.
Q: можно ли имитировать поведение распространения исключений
async
методы для несинхронныхTask
-основанные методы, так что последний не бросает на тот же кадр стека?A: если действительно нужно, то да, есть трюк для этого:
Примечание при определенных условиях (например, когда он слишком глубоко в стеке),// 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 Task TestAsync() { await Task.Delay(1000); }
и
Task TestAsync() { return Task.Delay(1000); }
?
меня смущает этот вопрос. Позвольте мне попытаться прояснить, ответив на ваш вопрос другим вопросом. В чем разница между ними?
Func<int> MakeFunction() { Func<int> f = ()=>1; return ()=>f(); }
и
Func<int> MakeFunction() { return ()=>1; }
?
что разница между двумя вещами есть разница между двумя вещами.
первый метод даже не компилируется.
С
Program.TestAsync()
- это асинхронный метод, который возвращает 'Task
', ключевое слово return не должно сопровождаться выражением объекта. Вы собирались вернуться?Task<T>
'?это должно быть
async Task TestAsync() { await Task.Run(() => DoSomeWork()); }
существует большая концептуальная разница между этими двумя. Первый-асинхронный, второй-нет. Чтение Асинхронной Производительности: Понимание затрат на асинхронность и ожидание чтобы получить немного больше о ВКУ
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 CS00 ) 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
два примера do отличаются. Когда метод помечен символом
async
ключевое слово, компилятор генерирует состояние машины за кулисами. Это то, что отвечает за возобновление продолжения, как только ожидаемое было ожидаемо.напротив, когда метод не С пометкой
async
вы теряете способностьawait
ожидание. (То есть внутри самого метода; метод все еще может быть ожидаем его вызывающим.) Однако, избегая элементasync
ключевое слово, вы больше не генерируете state-machine, который может добавить справедливый бит накладных расходов (подъем локальных объектов в поля state-machine, дополнительные объекты в GC).в таких примерах, если вы можете избежать
async-await
и возвращает ожидаемый объект непосредственно, это должно быть сделано для повышения эффективности метода.посмотреть этот вопрос и ответ которые очень похожи на ваш вопрос и этот ответ.