"이 비동기 메서드에는 'await'연산자가없고 동 기적으로 실행됩니다."경고에 대해 걱정해야합니까?


93

일부 비동기 메서드를 노출하는 인터페이스가 있습니다. 보다 구체적으로 Task 또는 Task <T>를 반환하는 메서드가 정의되어 있습니다. async / await 키워드를 사용하고 있습니다.

이 인터페이스를 구현하는 중입니다. 그러나 이러한 방법 중 일부에서는이 구현이 기다릴 것이 없습니다. 따라서 "이 비동기 메서드에는 '대기'연산자가 없으며 동 기적으로 실행됩니다."라는 컴파일러 경고가 표시됩니다.

나는 왜 오류가 발생하는지 이해하지만이 맥락에서 그들에 대해 어떤 조치를 취해야하는지 궁금합니다. 컴파일러 경고를 무시하는 것은 잘못된 느낌입니다.

Task.Run을 기다려서 고칠 수 있다는 것을 알고 있지만 몇 가지 저렴한 작업 만 수행하는 메서드에 대해서는 잘못된 느낌입니다. 또한 실행에 불필요한 오버 헤드를 추가하는 것처럼 들리지만 async 키워드가 있기 때문에 이것이 이미 존재하는지 확실하지 않습니다.

경고를 무시해야합니까, 아니면 표시되지 않는 문제를 해결하는 방법이 있습니까?


2
세부 사항에 따라 달라집니다. 이러한 작업을 동기식으로 수행 하시겠습니까? 동기식으로 수행되기를 원하는 경우 메서드가 왜 표시 async됩니까?
Servy 2015-04-28

11
async키워드를 제거하십시오 . 여전히 Taskusing을 반환 할 수 있습니다 Task.FromResult.
Michael Liu

1
@BenVoigt Google은 OP가 아직 알지 못하는 경우에 대비하여 이에 대한 정보로 가득 차 있습니다.
Servy 2015-04-28

1
@BenVoigt Michael Liu가 이미 그 힌트를 제공하지 않았습니까? 사용 Task.FromResult.

1
@hvd : 그것은 나중에 그의 코멘트에 편집되었습니다.
Ben Voigt 2015

답변:


144

비동기 단지 방법의 구현 세부 키워드이다; 메서드 서명의 일부가 아닙니다. 특정 메서드 구현 또는 재정의에 기다릴 것이없는 경우 async 키워드를 생략하고 Task.FromResult <TResult>를 사용하여 완료된 작업을 반환합니다 .

public Task<string> Foo()               //    public async Task<string> Foo()
{                                       //    {
    Baz();                              //        Baz();
    return Task.FromResult("Hello");    //        return "Hello";
}                                       //    }

메서드가 Task <TResult> 대신 Task 를 반환하는 경우 모든 유형 및 값의 완료된 작업을 반환 할 수 있습니다. Task.FromResult(0)인기있는 선택 인 것 같습니다.

public Task Bar()                       //    public async Task Bar()
{                                       //    {
    Baz();                              //        Baz();
    return Task.FromResult(0);          //
}                                       //    }

또는 .NET Framework 4.6부터 Task.CompletedTask 를 반환 할 수 있습니다 .

public Task Bar()                       //    public async Task Bar()
{                                       //    {
    Baz();                              //        Baz();
    return Task.CompletedTask;          //
}                                       //    }

감사합니다. 내가 놓친 부분은 async 키워드를 갖는 것과 동일한 실제 작업을 반환하는 대신 완료된 작업을 만드는 개념이라고 생각합니다. 지금 당연한 것 같지만 나는 그것을 보지 못했습니다!
dannykay1710 2015

1
Task는이를 위해 Task.Empty 라인을 따라 정적 멤버로 할 수 있습니다. 의도는 조금 더 명확 할 것이며, 필요하지 않은 0을 반환하는 이러한 모든 의무적 인 작업을 생각하는 것이 고통 스럽습니다.
Rupert Rawnsley 2016 년

await Task.FromResult(0)? 어때요 await Task.Yield()?
Sushi271 2016.10.23

1
@ Sushi271 : 아니요, 비 async메소드에서는 기다리는 대신 돌아옵니다 Task.FromResult(0) .
마이클 리우

1
실제로 아니요, 비동기는 구현 세부 사항이 아닙니다.주의해야 할 사항이 많이 있습니다. 어떤 부분이 동 기적으로 실행되는지, 어떤 부분이 비동기 적으로 실행되는지, 현재 동기화 컨텍스트가 무엇인지, 그리고 기록을 위해서만 작업은 커튼 뒤에있는 상태 시스템이 없기 때문에 항상 조금 더 빠릅니다.)
ipavlu

16

일부 "비동기"작업은 동기식으로 완료되지만 다형성을 위해 여전히 비동기 호출 모델을 따르는 것이 합리적입니다.

이에 대한 실제 예는 OS I / O API입니다. 일부 장치의 비동기 및 중첩 호출은 항상 인라인으로 완료됩니다 (예 : 공유 메모리를 사용하여 구현 된 파이프에 쓰기). 그러나 그들은 백그라운드에서 계속되는 다중 부분 작업과 동일한 인터페이스를 구현합니다.


4

Michael Liu는 Task.FromResult를 반환하여 경고를 피할 수있는 방법에 대한 질문에 잘 대답했습니다.

질문의 "경고에 대해 걱정해야합니까?"부분에 대답하겠습니다.

대답은 예입니다!

그 이유 Taskawait연산자 없이 비동기 메서드 내부 를 반환하는 메서드를 호출 할 때 경고가 자주 발생하기 때문 입니다. 이전 작업을 기다리지 않고 Entity Framework에서 작업을 호출했기 때문에 발생한 동시성 버그를 수정했습니다.

컴파일러 경고를 피하기 위해 코드를 꼼꼼하게 작성할 수 있다면 경고가있을 때 엄지 손가락처럼 눈에 띕니다. 몇 시간의 디버깅을 피할 수있었습니다.


5
이 대답은 틀 렸습니다. 그 이유는 다음과 같습니다. await한곳에 메서드 내부 에 적어도 하나 가있을 수 있지만 (CS1998이 없을 것입니다) 동기화가 부족한 다른 asnyc 메서드 호출이 없다는 것을 의미하지는 않습니다 (사용 await또는 기타 사용). 이제 누군가 실수로 동기화를 놓치지 않도록하는 방법을 알고 싶다면 다른 경고 인 CS4014를 무시하지 않도록하십시오. 나는 그것을 오류로 위협 할 것을 권장합니다.
Victor Yarema

3

너무 늦었지만 유용한 조사가 될 수 있습니다.

컴파일 된 코드 ( IL ) 의 내부 구조는 다음과 같습니다.

 public static async Task<int> GetTestData()
    {
        return 12;
    }

IL에서는 다음과 같이됩니다.

.method private hidebysig static class [mscorlib]System.Threading.Tasks.Task`1<int32> 
        GetTestData() cil managed
{
  .custom instance void [mscorlib]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [mscorlib]System.Type) = ( 01 00 28 55 73 61 67 65 4C 69 62 72 61 72 79 2E   // ..(UsageLibrary.
                                                                                                                                     53 74 61 72 74 54 79 70 65 2B 3C 47 65 74 54 65   // StartType+<GetTe
                                                                                                                                     73 74 44 61 74 61 3E 64 5F 5F 31 00 00 )          // stData>d__1..
  .custom instance void [mscorlib]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() = ( 01 00 00 00 ) 
  // Code size       52 (0x34)
  .maxstack  2
  .locals init ([0] class UsageLibrary.StartType/'<GetTestData>d__1' V_0,
           [1] valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> V_1)
  IL_0000:  newobj     instance void UsageLibrary.StartType/'<GetTestData>d__1'::.ctor()
  IL_0005:  stloc.0
  IL_0006:  ldloc.0
  IL_0007:  call       valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<!0> valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::Create()
  IL_000c:  stfld      valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> UsageLibrary.StartType/'<GetTestData>d__1'::'<>t__builder'
  IL_0011:  ldloc.0
  IL_0012:  ldc.i4.m1
  IL_0013:  stfld      int32 UsageLibrary.StartType/'<GetTestData>d__1'::'<>1__state'
  IL_0018:  ldloc.0
  IL_0019:  ldfld      valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> UsageLibrary.StartType/'<GetTestData>d__1'::'<>t__builder'
  IL_001e:  stloc.1
  IL_001f:  ldloca.s   V_1
  IL_0021:  ldloca.s   V_0
  IL_0023:  call       instance void valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::Start<class UsageLibrary.StartType/'<GetTestData>d__1'>(!!0&)
  IL_0028:  ldloc.0
  IL_0029:  ldflda     valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> UsageLibrary.StartType/'<GetTestData>d__1'::'<>t__builder'
  IL_002e:  call       instance class [mscorlib]System.Threading.Tasks.Task`1<!0> valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::get_Task()
  IL_0033:  ret
} // end of method StartType::GetTestData

비동기 및 작업 방법없이 :

 public static int GetTestData()
        {
            return 12;
        }

됩니다 :

.method private hidebysig static int32  GetTestData() cil managed
{
  // Code size       8 (0x8)
  .maxstack  1
  .locals init ([0] int32 V_0)
  IL_0000:  nop
  IL_0001:  ldc.i4.s   12
  IL_0003:  stloc.0
  IL_0004:  br.s       IL_0006
  IL_0006:  ldloc.0
  IL_0007:  ret
} // end of method StartType::GetTestData

이 방법의 큰 차이점을 알 수 있습니다. 비동기 메서드 내에서 await를 사용하지 않고 비동기 메서드 (예 : API 호출 또는 이벤트 처리기) 사용에 신경 쓰지 않는 경우 좋은 아이디어는이를 일반 동기화 메서드로 변환합니다 (애플리케이션 성능을 절약 함).

업데이트 :

Microsoft Docs https://docs.microsoft.com/en-us/dotnet/standard/async-in-depth의 추가 정보도 있습니다 .

비동기 메서드는 본문에 await 키워드가 있어야합니다. 이것은 명심해야 할 중요합니다. await가 비동기 메서드의 본문에 사용되지 않는 경우 C # 컴파일러는 경고를 생성하지만 코드는 일반적인 메서드 인 것처럼 컴파일 및 실행됩니다. 비동기 메서드에 대해 C # 컴파일러에 의해 생성 된 상태 머신이 아무 것도 수행하지 못하기 때문에 이것은 매우 비효율적입니다.


2
추가적으로 사용에 대한 최종 결론 async/await은 CPU 바운드 단일 작업의 비현실적인 예를 기반으로하므로 지나치게 단순화됩니다. Tasks를 적절히 사용하면 동시 작업 (예 : 병렬) 및 스레드의 더 나은 관리 및 사용으로 인해 애플리케이션 성능 및 응답 성이 향상됩니다.
MickyD

이 게시물에서 말했듯이 테스트 단순화 예제입니다. 또한 두 버전의 메서드 (비동기 및 일반)를 사용하여 가능한 경우 API 및 이벤트 헨들러에 대한 요청에 대해 언급했습니다. 또한 PO는 내부에서 대기하지 않고 비동기 메서드를 사용하는 것에 대해 말했습니다. 내 게시물은 그것에 관한 것이지만 Tasks. 게시물의 전체 내용을 읽지 않고 빠르게 결론을 내리는 것은 슬픈 이야기입니다.
Oleg Bondarenko 19

1
int(귀하의 경우 Task와 같이 ) 반환하는 메서드 와 OP에서 논의한 것과 같이 반환하는 메서드에는 차이가 있습니다 . 개인적으로 생각하는 대신 그의 게시물과 수락 된 답변을 다시 읽으십시오 . 이 경우 귀하의 답변은 도움이되지 않습니다. await내부에 있는 방법의 차이를 보여주지 않아도됩니다. 이제 당신은이되었을 것이라고했던 아주 좋은 잘만한 가치 upvote에
MickyD

비동기 메서드와 API 또는 이벤트 처리기로 호출되는 일반 메서드의 차이점을 이해하지 못하는 것 같습니다. 내 게시물에서 특별히 언급되었습니다. 다시 놓쳐서 죄송합니다 .
Oleg Bondarenko 19

1

반환시 예외 동작에 대한 참고 사항 Task.FromResult

다음은로 표시된 메서드와 표시되지 않은 메서드 간의 예외 처리 차이를 보여주는 간단한 데모입니다 async.

public Task<string> GetToken1WithoutAsync() => throw new Exception("Ex1!");

// Warning: This async method lacks 'await' operators and will run synchronously. Consider ...
public async Task<string> GetToken2WithAsync() => throw new Exception("Ex2!");  

public string GetToken3Throws() => throw new Exception("Ex3!");
public async Task<string> GetToken3WithAsync() => await Task.Run(GetToken3Throws);

public async Task<string> GetToken4WithAsync() { throw new Exception("Ex4!"); return await Task.FromResult("X");} 


public static async Task Main(string[] args)
{
    var p = new Program();

    try { var task1 = p.GetToken1WithoutAsync(); } 
    catch( Exception ) { Console.WriteLine("Throws before await.");};

    var task2 = p.GetToken2WithAsync(); // Does not throw;
    try { var token2 = await task2; } 
    catch( Exception ) { Console.WriteLine("Throws on await.");};

    var task3 = p.GetToken3WithAsync(); // Does not throw;
    try { var token3 = await task3; } 
    catch( Exception ) { Console.WriteLine("Throws on await.");};

    var task4 = p.GetToken4WithAsync(); // Does not throw;
    try { var token4 = await task4; } 
    catch( Exception ) { Console.WriteLine("Throws on await.");};
}
// .NETCoreApp,Version=v3.0
Throws before await.
Throws on await.
Throws on await.
Throws on await.

( When async Task <T> required by interface, how to get variable return without compiler warning )에 대한 내 대답의 교차 게시물

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