224

.NET 용 Microsoft의 비동기 CTP를 사용하면, 호출하는 메서드에서 비동기 메서드에 의해 throw 된 예외를 catch 할 수 있습니까?

public async void Foo()
{
    var x = await DoSomethingAsync();

    /* Handle the result, but sometimes an exception might be thrown.
       For example, DoSomethingAsync gets data from the network
       and the data is invalid... a ProtocolException might be thrown. */
}

public void DoFoo()
{
    try
    {
        Foo();
    }
    catch (ProtocolException ex)
    {
          /* The exception will never be caught.
             Instead when in debug mode, VS2010 will warn and continue.
             The deployed the app will simply crash. */
    }
}

그래서 기본적으로 비동기 코드의 예외를 호출 코드로 버블 링하기를 원합니다. 그것이 심지어 가능하다면.


  • 이게 당신에게 도움이됩니까?social.msdn.microsoft.com/Forums/en/async/thread/… - svrist
  • 미래에 누군가가이 문제에 직면하면비동기 / 대기 모범 사례 ... 문서"그림 2 비동기 무효화 방법의 예외는 캐치를 잡을 수 없습니다."에서 잘 설명되어 있습니다. "비동기 태스크 또는 비동기 태스크 < T > 메서드를 호출하면 해당 예외가 캡처되어 Task 개체에 배치됩니다. 비동기 void 메서드를 사용하면 Task 객체가 없으므로 async void 메서드에서 throw 된 예외는 async void 메서드가 시작될 때 활성화 된 SynchronizationContext에서 직접 발생합니다." - Mr Moose
  • 당신이 사용할 수있는이 접근법또는 - Tselofan

6 답변


206

읽는 것이 다소 이상하지만 예외는 호출 코드에 버블 링됩니다.만약 너라면await또는Wait()~에 대한 전화Foo.

public async void DoFoo()
{
    try
    {
        await Foo();
    }
    catch (ProtocolException ex)
    {
          // The exception will be caught because you've awaited
          // the call in an async method.
    }
}

//or//

public void DoFoo()
{
    try
    {
        Foo().Wait();
    }
    catch (ProtocolException ex)
    {
          /* The exception will be caught because you've
             waited for the completion of the call. */
    }
} 

비동기 void 메 소드는 다른 오류 처리 의미론을가집니다. 비동기 작업 또는 비동기 작업 메서드에서 예외가 throw되면 해당 예외가 캡처되어 작업 개체에 배치됩니다. 비동기 void 메서드를 사용하면 Task 객체가 없기 때문에 비동기 void 메서드에서 throw 된 예외는 async void 메서드가 시작될 때 활성화 된 SynchronizationContext에서 직접 발생합니다. -https://msdn.microsoft.com/en-us/magazine/jj991977.aspx

Wait ()를 사용하면 .Net이 메서드를 동 기적으로 실행하기로 결정하면 응용 프로그램이 차단 될 수 있습니다.

이 설명http://www.interact-sw.co.uk/iangblog/2010/11/01/csharp5-async-exceptions컴파일러가이 마법을 달성하는 데 필요한 단계를 설명합니다.


  • 실제로 읽는 것이 쉽습니다. 실제로 진행되는 작업이 실제로 복잡하다는 것을 알고 있지만 내 뇌가 내 눈을 믿지 말라고 말하고 있습니다 ... - Stuart
  • 해야할까요?DoFoo()표시되다async이리? - zmb
  • Foo () 메서드는 void 대신 Task로 표시되어야한다고 생각합니다. - Sornii
  • & quot; 또는 Foo에 대한 호출을 기다리거나 () 기다릴 때만 & quot; 너 어떻게 할 수있어?awaitFoo가 무효로 돌아 왔을 때 Foo에 대한 호출?async void Foo().Type void is not awaitable? - rism
  • void 메소드를 기다릴 수 없습니까? - Hitesh P

64

예외가 잡히지 않는 이유는 Foo () 메소드가 void 리턴 유형을 가지므로 대기 요청이 호출 될 때 단순히 리턴합니다. DoFoo ()가 Foo의 완료를 기다리지 않기 때문에 예외 처리기를 사용할 수 없습니다.

메서드 시그니처를 변경할 수있는 경우 간단한 솔루션이 열립니다.Foo()그래서 그것은 타입을 반환한다.Task그리고DoFoo()양철통await Foo(),이 코드에서와 같이 :

public async Task Foo() {
    var x = await DoSomethingThatThrows();
}

public async void DoFoo() {
    try {
        await Foo();
    } catch (ProtocolException ex) {
        // This will catch exceptions from DoSomethingThatThrows
    }
}


  • 이것은 실제로 당신에게 몰래 들어가고 컴파일러에 의해 경고되어야합니다. - GGleGrand

16

귀하의 코드는 귀하가 생각하는대로하지 않습니다. 메서드가 비동기 결과를 기다리는 즉시 비동기 메서드가 반환됩니다. 코드가 실제로 어떻게 작동하는지 조사하기 위해 추적을 사용하는 것은 통찰력이 있습니다.

아래 코드는 다음을 수행합니다.

  • 4 가지 작업 만들기
  • 각 작업은 비동기 적으로 숫자를 증가시키고 증가 된 숫자를 반환합니다.
  • 비동기 결과가 도착하면 추적됩니다.

& nbsp;

static TypeHashes _type = new TypeHashes(typeof(Program));        
private void Run()
{
    TracerConfig.Reset("debugoutput");

    using (Tracer t = new Tracer(_type, "Run"))
    {
        for (int i = 0; i < 4; i++)
        {
            DoSomeThingAsync(i);
        }
    }
    Application.Run();  // Start window message pump to prevent termination
}


private async void DoSomeThingAsync(int i)
{
    using (Tracer t = new Tracer(_type, "DoSomeThingAsync"))
    {
        t.Info("Hi in DoSomething {0}",i);
        try
        {
            int result = await Calculate(i);
            t.Info("Got async result: {0}", result);
        }
        catch (ArgumentException ex)
        {
            t.Error("Got argument exception: {0}", ex);
        }
    }
}

Task<int> Calculate(int i)
{
    var t = new Task<int>(() =>
    {
        using (Tracer t2 = new Tracer(_type, "Calculate"))
        {
            if( i % 2 == 0 )
                throw new ArgumentException(String.Format("Even argument {0}", i));
            return i++;
        }
    });
    t.Start();
    return t;
}

흔적을 관찰 할 때

22:25:12.649  02172/02820 {          AsyncTest.Program.Run 
22:25:12.656  02172/02820 {          AsyncTest.Program.DoSomeThingAsync     
22:25:12.657  02172/02820 Information AsyncTest.Program.DoSomeThingAsync Hi in DoSomething 0    
22:25:12.658  02172/05220 {          AsyncTest.Program.Calculate    
22:25:12.659  02172/02820 {          AsyncTest.Program.DoSomeThingAsync     
22:25:12.659  02172/02820 Information AsyncTest.Program.DoSomeThingAsync Hi in DoSomething 1    
22:25:12.660  02172/02756 {          AsyncTest.Program.Calculate    
22:25:12.662  02172/02820 {          AsyncTest.Program.DoSomeThingAsync     
22:25:12.662  02172/02820 Information AsyncTest.Program.DoSomeThingAsync Hi in DoSomething 2    
22:25:12.662  02172/02820 {          AsyncTest.Program.DoSomeThingAsync     
22:25:12.662  02172/02820 Information AsyncTest.Program.DoSomeThingAsync Hi in DoSomething 3    
22:25:12.664  02172/02756          } AsyncTest.Program.Calculate Duration 4ms   
22:25:12.666  02172/02820          } AsyncTest.Program.Run Duration 17ms  ---- Run has completed. The async methods are now scheduled on different threads. 
22:25:12.667  02172/02756 Information AsyncTest.Program.DoSomeThingAsync Got async result: 1    
22:25:12.667  02172/02756          } AsyncTest.Program.DoSomeThingAsync Duration 8ms    
22:25:12.667  02172/02756 {          AsyncTest.Program.Calculate    
22:25:12.665  02172/05220 Exception   AsyncTest.Program.Calculate Exception thrown: System.ArgumentException: Even argument 0   
   at AsyncTest.Program.c__DisplayClassf.Calculateb__e() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 124   
   at System.Threading.Tasks.Task`1.InvokeFuture(Object futureAsObj)    
   at System.Threading.Tasks.Task.InnerInvoke()     
   at System.Threading.Tasks.Task.Execute()     
22:25:12.668  02172/02756 Exception   AsyncTest.Program.Calculate Exception thrown: System.ArgumentException: Even argument 2   
   at AsyncTest.Program.c__DisplayClassf.Calculateb__e() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 124   
   at System.Threading.Tasks.Task`1.InvokeFuture(Object futureAsObj)    
   at System.Threading.Tasks.Task.InnerInvoke()     
   at System.Threading.Tasks.Task.Execute()     
22:25:12.724  02172/05220          } AsyncTest.Program.Calculate Duration 66ms      
22:25:12.724  02172/02756          } AsyncTest.Program.Calculate Duration 57ms      
22:25:12.725  02172/05220 Error       AsyncTest.Program.DoSomeThingAsync Got argument exception: System.ArgumentException: Even argument 0  

Server stack trace:     
   at AsyncTest.Program.c__DisplayClassf.Calculateb__e() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 124   
   at System.Threading.Tasks.Task`1.InvokeFuture(Object futureAsObj)    
   at System.Threading.Tasks.Task.InnerInvoke()     
   at System.Threading.Tasks.Task.Execute()     

Exception rethrown at [0]:      
   at System.Runtime.CompilerServices.TaskAwaiter.EndAwait()    
   at System.Runtime.CompilerServices.TaskAwaiter`1.EndAwait()  
   at AsyncTest.Program.DoSomeThingAsyncd__8.MoveNext() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 106    
22:25:12.725  02172/02756 Error       AsyncTest.Program.DoSomeThingAsync Got argument exception: System.ArgumentException: Even argument 2  

Server stack trace:     
   at AsyncTest.Program.c__DisplayClassf.Calculateb__e() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 124   
   at System.Threading.Tasks.Task`1.InvokeFuture(Object futureAsObj)    
   at System.Threading.Tasks.Task.InnerInvoke()     
   at System.Threading.Tasks.Task.Execute()     

Exception rethrown at [0]:      
   at System.Runtime.CompilerServices.TaskAwaiter.EndAwait()    
   at System.Runtime.CompilerServices.TaskAwaiter`1.EndAwait()  
   at AsyncTest.Program.DoSomeThingAsyncd__8.MoveNext() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 0      
22:25:12.726  02172/05220          } AsyncTest.Program.DoSomeThingAsync Duration 70ms   
22:25:12.726  02172/02756          } AsyncTest.Program.DoSomeThingAsync Duration 64ms   
22:25:12.726  02172/05220 {          AsyncTest.Program.Calculate    
22:25:12.726  02172/05220          } AsyncTest.Program.Calculate Duration 0ms   
22:25:12.726  02172/05220 Information AsyncTest.Program.DoSomeThingAsync Got async result: 3    
22:25:12.726  02172/05220          } AsyncTest.Program.DoSomeThingAsync Duration 64ms   

하나의 자식 스레드 만 완료되면 Run 메서드가 스레드 2820에서 완료됨을 알 수 있습니다 (2756). 만약 당신이 await 메소드를 try / catch한다면, 계산 태스크가 끝나고 contiuation이 실행될 때 코드가 다른 쓰레드에서 실행 되더라도 예외를 "catch"할 수 있습니다.

ApiChange.Api.dll을 사용했기 때문에 계산 방법은 발생 된 예외를 자동으로 추적합니다.아피 체인지수단. Tracing과 Reflector는 무슨 일이 일어나고 있는지 이해하는 데 많은 도움이됩니다. 스레딩을 없애기 위해 GetAwaiter BeginAwait 및 EndAwait의 고유 버전을 생성하고 작업을 감쌀 수 있습니다 (예 : 너의 자신의 연장 방법 안에 게으르고 흔적. 그러면 컴파일러와 TPL이하는 일을 훨씬 더 잘 이해하게 될 것입니다.

이제 아무 예외도 전파 할 스택 프레임이 없으므로 try / catch 예외를 잡을 방법이 없다는 것을 알 수 있습니다. 비동기 작업을 시작한 후에 코드가 완전히 다른 작업을 수행하고있을 수 있습니다. Thread.Sleep을 호출하거나 종료 할 수 있습니다. 하나의 포 그라운드 스레드가 남아있는 한 애플리케이션은 행복하게 비동기 태스크를 계속 실행합니다.


비동기 작업이 끝나고 UI 스레드로 다시 콜백 한 후에 async 메서드 내에서 예외를 처리 할 수 있습니다. 이렇게하는 것이 좋습니다.TaskScheduler.FromSynchronizationContext. 그것은 UI 스레드가 있고 다른 것들과 함께 매우 바쁘지 않은 경우에만 작동합니다.


4

또한 비동기 메서드에서 void 반환 유형을 사용하는 경우 예외의 연대순 스택 추적을 잃게된다는 점에 유의해야합니다. 다음과 같이 Task를 반환하는 것이 좋습니다. 디버깅을 훨씬 쉽게 할 수 있습니다.

public async Task DoFoo()
    {
        try
        {
            return await Foo();
        }
        catch (ProtocolException ex)
        {
            /* Exception with chronological stack trace */     
        }
    }


3

예외는 async 함수에서 잡힐 수 있습니다.

public async void Foo()
{
    try
    {
        var x = await DoSomethingAsync();
        /* Handle the result, but sometimes an exception might be thrown
           For example, DoSomethingAsync get's data from the network
           and the data is invalid... a ProtocolException might be thrown */
    }
    catch (ProtocolException ex)
    {
          /* The exception will be caught here */
    }
}

public void DoFoo()
{
    Foo();
}


  • 이봐 요, 알아요.하지만 UI에 정보를 표시 할 수 있도록 DoFoo에 정보가 필요합니다. 이 경우 UI가 최종 사용자 도구가 아니기 때문에 예외를 표시하는 것이 중요하지만 통신 프로토콜을 디버그하는 도구입니다 - TimothyP
  • 이 경우 콜백은 많은 의미를 갖습니다. (오래된 비동기 델리게이트) - Sanjeevakumar Hiremath
  • @Tim : 던져진 예외에 필요한 정보를 포함 시키시겠습니까? - Eric J.

1

이 블로그는 문제를 깔끔하게 설명합니다.비동기 모범 사례.

비동기 이벤트 처리기가 아닌 한 비동기 메서드에 대한 반환 값으로 void를 사용해서는 안됩니다. 예외가 잡히지 않기 때문에 나쁜 관행입니다.

가장 좋은 방법은 반환 유형을 작업으로 변경하는 것입니다. 또한 모든 방법으로 비동기를 코딩하고 모든 비동기 메서드 호출을 만들고 비동기 메서드에서 호출하도록하십시오. 콘솔에서 Main 메서드를 제외하고는 비동기가 될 수 없습니다 (C # 7.1 이전).

이 모범 사례를 무시하면 GUI 및 ASP.NET 응용 프로그램으로 교착 상태가 발생합니다. 교착 상태는 이러한 응용 프로그램이 하나의 스레드 만 허용하고 비동기 스레드에 전달하지 않는 컨텍스트에서 실행되기 때문에 발생합니다. 즉, GUI는 반환을 위해 동 기적으로 대기하며 비동기 메소드는 컨텍스트 : 교착 상태를 기다립니다.

이 동작은 스레드 풀과 함께 컨텍스트에서 실행되므로 콘솔 응용 프로그램에서는 발생하지 않습니다. 비동기 메서드는 예약 될 다른 스레드에서 반환됩니다. 이것은 테스트 콘솔 응용 프로그램이 작동하지만 다른 응용 프로그램에서 동일한 호출이 교착 상태가되는 이유입니다 ...


  • & quot; 콘솔의 기본 방법을 제외하고는 비동기 성일 수 없습니다 & quot;C # 7.1부터 Main은 이제 비동기 메소드가 될 수 있습니다.링크 - Adam

연결된 질문


관련된 질문

최근 질문