Любая разница между " await Task.Run (); return; " и " задача возврата.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 восстанавливается даже при отсутствии асинхронности. Почему?
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);
}
природа мертвого замка хорошо объяснена Стивен Клири в своем блог.
Еще одно существенное различие заключается в распространение исключения. исключение, брошенное внутри
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
Примечание, я должен был нажать Enter чтобы увидеть его.
теперь, если я называю DoTestAsync(AnotherTestAsync, -2)
рабочий процесс внутри DoTestAsync
совсем другое, и поэтому выход. На этот раз меня не просили нажимать Enter:
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 не должно следовать выражение объекта. Вы намеревались вернуться?--5-->'?это должно быть
async Task TestAsync() { await Task.Run(() => DoSomeWork()); }
существует большая концептуальная разница между этими двумя. Первый-асинхронный, второй-нет. Чтение Асинхронной Производительности: Понимание стоимости Async и 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 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
awaitables. (То есть внутри самого метода; метод все еще может быть ожидаем его вызывающим.) Однако, избегая the async
ключевое слово, вы больше не генерируете state-machine, который может добавить справедливый бит накладных расходов (подъем местных жителей в поля state-machine, дополнительные объекты в GC).
в примерах, подобных этому, если вы можете избежать async-await
и вернуть ожидаемый напрямую, это должно быть сделано для повышения эффективности метода.
посмотреть этот вопрос и ответ которые очень похожи на ваш вопрос и этот ответ.