[c#] “await Task.Run (); 반환;” 그리고“Return Task.Run ()”?

다음 두 코드 사이에 개념적 차이가 있습니까?

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됩니다.

다음 코드를 고려 곳 OneTestAsyncAnotherTestAsync상당히 다르게 동작 :

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 응용 프로그램에서 교착 상태가됩니다.asyncawait

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;
}

?

내 두 가지의 차이점이 무엇이든간에 동일한 차이는 두 가지입니다.


답변

  1. 첫 번째 방법은 컴파일도하지 않습니다.

    Program.TestAsync()‘는 ‘ ‘를 반환하는 비동기 메서드이므로 반환 Task키워드 뒤에 개체식이 올 수 없습니다. ‘ Task<T>‘ 을 (를) 반환하려고 했습니까 ?

    그건 그래야만 해

    async Task TestAsync()
    {
        await Task.Run(() => DoSomeWork());
    }
    
  2. 이 둘 사이에는 큰 개념적 차이가 있습니다. 첫 번째는 비동기식이고 두 번째는 비동기식입니다. Async Performance : Understanding the Costs of Async and Await to get more about internals of 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/'<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을 직접 피하고 반환 할 수 있다면 메서드의 효율성을 높이기 위해 수행해야합니다.

이 질문 과 귀하의 질문 및이 답변과 매우 유사한 이 답변 을 참조하십시오 .


답변