다음 두 코드 사이에 개념적 차이가 있습니까?
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는 비동기가없는 경우에도 복원됩니다. 왜?
답변
한 가지 주요 차이점은 예외 전파입니다. 내부 던져진 예외 상황, async Task
방법, 반환에 저장됩니다 Task
객체와 작업을 통해 관찰 때까지 휴면 남아 await task
, task.Wait()
, task.Result
또는 task.GetAwaiter().GetResult()
. 메서드 의 동기 부분 에서 throw 되더라도 이러한 방식으로 전파 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 키를 누르세요. 오류 : 하나 이상의 오류가 발생했습니다. await Task.Delay 오류 : 두 번째
나는 Enter그것을보기 위해 눌러야 했다.
이제를 호출하면 DoTestAsync(AnotherTestAsync, -2)
내부의 코드 워크 플로 DoTestAsync
가 상당히 다르며 출력도 마찬가지입니다. 이번에는 다음을 누르라는 요청을받지 않았습니다 Enter.
오류 : 값은 -1 (무한 제한 시간을 의미), 0 또는 양의 정수 여야합니다. 매개 변수 이름 : millisecondsDelayError : 1st
두 경우 모두 Task.Delay(-2)
매개 변수의 유효성을 검사하면서 처음에 발생합니다. 이것은 구성 시나리오 일 수 있지만 이론적으로 Task.Delay(1000)
는 예를 들어 기본 시스템 타이머 API가 실패하는 경우에도 발생할 수 있습니다.
참고로, 오류 전파 논리는 async void
메서드 ( 메소드 와 반대)에 대해 아직 다릅니다 async Task
. async void
메서드 내에서 발생한 예외 SynchronizationContext.Post
는 현재 스레드가 하나 ( SynchronizationContext.Current != null)
. 그렇지 않으면를 통해 다시 throw됩니다 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
여전히 비동기 적으로 실행될 수 있습니다.
또 다른 주목할만한 차이가 있다는 것입니다 /의 버전은 죽은 잠금이 아닌 기본 동기화 상황에 더 많은 경향이있다 . 예를 들어 다음은 WinForms 또는 WPF 응용 프로그램에서 교착 상태가됩니다.async
await
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);
}
교착 상태의 특성은 그의 블로그 에서 Stephen Cleary에 의해 잘 설명됩니다 .
답변
차이점은 무엇입니까
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
키워드 뒤에 개체식이 올 수 없습니다. ‘Task<T>
‘ 을 (를) 반환하려고 했습니까 ?그건 그래야만 해
async Task TestAsync() { await Task.Run(() => DoSomeWork()); }
-
이 둘 사이에는 큰 개념적 차이가 있습니다. 첫 번째는 비동기식이고 두 번째는 비동기식입니다. Async Performance : Understanding the Costs of Async and Await to get more about internals of
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
키워드 로 표시 되면 컴파일러는 백그라운드에서 상태 머신을 생성합니다. 이것은 awaitable이 기다린 후에 연속을 재개하는 책임입니다.
대조적으로, 메소드가 표시 되지 않으면 대기 가능 async
기능을 await
잃게됩니다. (즉, 메서드 자체 내에서 메서드는 호출자가 계속 기다릴 수 있습니다.) 그러나 async
키워드 를 피하면 더 이상 상태 머신을 생성하지 않아 상당한 오버 헤드를 추가 할 수 있습니다 (로컬을 필드로 끌어 올림). 상태 머신, GC에 대한 추가 개체).
이와 같은 예 async-await
에서 awaitable을 직접 피하고 반환 할 수 있다면 메서드의 효율성을 높이기 위해 수행해야합니다.