“await Task.Run (); 반환;" 그리고“Return Task.Run ()”?


90

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

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는 비동기가없는 경우에도 복원됩니다. 왜?


1
네, 다릅니다. 그리고 그것은 많이 다릅니다. 그렇지 않으면 사용하여 아무 소용이 없을 것입니다 await/를 async모든 :)에서
MarcinJuraszek

1
여기에 두 가지 질문 이 있다고 생각 합니다. 1. 메서드의 실제 구현이 호출자에게 중요합니까? 2. 두 방법의 컴파일 된 표현이 다른가요?
DavidRR

답변:


80

한 가지 주요 차이점은 예외 전파입니다. 내부 던져진 예외 상황, 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에 의해 잘 설명됩니다 .


2
첫 번째 예제의 교착 상태는 await 줄에 .ConfigureAwait (false)를 추가하여 피할 수 있다고 생각합니다. 메서드가 동일한 실행 컨텍스트로 돌아 가려고하기 때문에 발생하기 때문입니다. 따라서 예외는 남아있는 유일한 차이점입니다.
relative_random

2
대답의 차이에 대해 있었지만 @relatively_random, 당신의 의견은 정확 return Task.Run()하고 await Task.Run(); return보다는,await Task.Run().ConfigureAwait(false); return
noseratio

Enter 키를 누른 후 프로그램이 닫히면 F5 대신 ctrl + F5를 입력해야합니다.
David Klempfner

54

차이점은 무엇입니까

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

?

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


22
물론이야! 당신은 내 눈을 떴습니다 :) 첫 번째 경우, 나는 의미 상 Task.Delay(1000).ContinueWith(() = {}). 두 번째에서는 Task.Delay(1000). 차이는 다소 미묘하지만 중요합니다.
avo

3
차이점을 조금 설명해 주시겠습니까? 사실은 내가 당신에게 don't..Thank
청 유를

4
동기화 컨텍스트와 예외 전파에 미묘한 차이가 있다는 점을 감안할 때 비동기 / 대기 및 함수 래퍼의 차이가 동일하지 않다고 말하고 싶습니다.
Cameron MacFarland

1
@CameronMacFarland : 그래서 설명을 요청했습니다. 질문은 둘 사이에 개념적 차이 가 있다는 것 입니다. 글쎄요. 확실히 많은 차이점이 있습니다. 그들 중 어떤 것이 "개념적"차이로 간주됩니까? 중첩 된 함수를 사용하는 제 예에서는 오류 전파에도 차이가 있습니다. 함수가 로컬 상태에 대해 닫히면 로컬 수명 등에 차이가 있습니다. 이러한 "개념적"차이점이 있습니까?
Eric Lippert

6
이것은 오래된 대답이지만 오늘 주어진다면 반대 투표되었을 것이라고 믿습니다. 그것은 질문에 대한 답도 아니고, 그가 배울 수있는 출처를 OP에 지적하지도 않습니다.
Daniel Dubovski

11
  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
    

@MarcinJuraszek, 실제로 컴파일되지 않았습니다. 오타였습니다. 맞았을 것입니다. 그렇지 않으면 훌륭한 답변입니다. 감사합니다! 나는 C #이 첫 번째 경우에 상태 머신 클래스를 생성하지 않도록 충분히 똑똑 할 것이라고 생각했습니다.
avo 2014 년

9

두 가지 예 다릅니다. 메서드가 async키워드 로 표시 되면 컴파일러는 백그라운드에서 상태 머신을 생성합니다. 이것은 awaitable이 기다린 후에 연속을 재개하는 책임입니다.

대조적으로, 메소드가 표시 되지 않으면 대기 가능 async기능을 await잃게됩니다. (즉, 메서드 자체 내에서 메서드는 호출자가 계속 기다릴 수 있습니다.) 그러나 async키워드 를 피하면 더 이상 상태 머신을 생성하지 않아 상당한 오버 헤드를 추가 할 수 있습니다 (로컬을 필드로 끌어 올림). 상태 머신, GC에 대한 추가 개체).

이와 같은 예 async-await에서 awaitable을 직접 피하고 반환 할 수 있다면 메서드의 효율성을 높이기 위해 수행해야합니다.

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.