.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. */
}
}
그래서 기본적으로 비동기 코드의 예외를 호출 코드로 버블 링하기를 원합니다. 그것이 심지어 가능하다면.
읽는 것이 다소 이상하지만 예외는 호출 코드에 버블 링됩니다.만약 너라면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컴파일러가이 마법을 달성하는 데 필요한 단계를 설명합니다.
DoFoo()
표시되다async
이리? - zmbawait
Foo가 무효로 돌아 왔을 때 Foo에 대한 호출?async void Foo()
.Type void is not awaitable
? - rism
예외가 잡히지 않는 이유는 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
}
}
귀하의 코드는 귀하가 생각하는대로하지 않습니다. 메서드가 비동기 결과를 기다리는 즉시 비동기 메서드가 반환됩니다. 코드가 실제로 어떻게 작동하는지 조사하기 위해 추적을 사용하는 것은 통찰력이 있습니다.
아래 코드는 다음을 수행합니다.
& 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 스레드가 있고 다른 것들과 함께 매우 바쁘지 않은 경우에만 작동합니다.
또한 비동기 메서드에서 void 반환 유형을 사용하는 경우 예외의 연대순 스택 추적을 잃게된다는 점에 유의해야합니다. 다음과 같이 Task를 반환하는 것이 좋습니다. 디버깅을 훨씬 쉽게 할 수 있습니다.
public async Task DoFoo()
{
try
{
return await Foo();
}
catch (ProtocolException ex)
{
/* Exception with chronological stack trace */
}
}
예외는 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();
}
이 블로그는 문제를 깔끔하게 설명합니다.비동기 모범 사례.
비동기 이벤트 처리기가 아닌 한 비동기 메서드에 대한 반환 값으로 void를 사용해서는 안됩니다. 예외가 잡히지 않기 때문에 나쁜 관행입니다.
가장 좋은 방법은 반환 유형을 작업으로 변경하는 것입니다. 또한 모든 방법으로 비동기를 코딩하고 모든 비동기 메서드 호출을 만들고 비동기 메서드에서 호출하도록하십시오. 콘솔에서 Main 메서드를 제외하고는 비동기가 될 수 없습니다 (C # 7.1 이전).
이 모범 사례를 무시하면 GUI 및 ASP.NET 응용 프로그램으로 교착 상태가 발생합니다. 교착 상태는 이러한 응용 프로그램이 하나의 스레드 만 허용하고 비동기 스레드에 전달하지 않는 컨텍스트에서 실행되기 때문에 발생합니다. 즉, GUI는 반환을 위해 동 기적으로 대기하며 비동기 메소드는 컨텍스트 : 교착 상태를 기다립니다.
이 동작은 스레드 풀과 함께 컨텍스트에서 실행되므로 콘솔 응용 프로그램에서는 발생하지 않습니다. 비동기 메서드는 예약 될 다른 스레드에서 반환됩니다. 이것은 테스트 콘솔 응용 프로그램이 작동하지만 다른 응용 프로그램에서 동일한 호출이 교착 상태가되는 이유입니다 ...