async / await-언제 작업 대 무효를 반환합니까?


502

어떤 시나리오에서 사용하고 싶습니까?

public async Task AsyncMethod(int num)

대신에

public async void AsyncMethod(int num)

당신의 진행 상황을 추적 할 수 있도록 작업을해야 할 경우 내가 생각할 수있는 유일한 시나리오입니다.

또한, 다음과 같은 방법으로, 비동기 및 await를 키워드는 불필요하다?

public static async void AsyncMethod2(int num)
{
    await Task.Factory.StartNew(() => Thread.Sleep(num));
}

20
비동기 방법이 있어야합니다 항상 이름 Async.Example 접미사 될 Foo()될 것입니다 FooAsync().
Fred

30
@Fred 주로, 항상 그런 것은 아닙니다. 이것은 컨벤션 일 뿐이며이 컨벤션에 허용되는 예외는 이벤트 기반 클래스 또는 인터페이스 계약과 관련이 있습니다 (MSDN 참조) . 예를 들어, Button1_Click과 같은 일반적인 이벤트 처리기의 이름을 바꾸지 않아야합니다.
Ben

14
그냥 메모는 사용하지 않아야 Thread.Sleep당신의 작업을 당신이해야 await Task.Delay(num)하는 대신
밥 베일

45
@fred 나는 이것에 동의하지 않는다. 비동기 접미사를 추가하는 IMO는 동기화 및 비동기 옵션이 모두있는 인터페이스를 제공 할 때만 사용해야합니다. 하나의 의도가있을 때 비동기로 스머프 이름을 지정하는 것은 의미가 없습니다. 작업의 모든 방법이 비동기 적이기 때문에 적절한 사례 Task.Delay는 아닙니다Task.AsyncDelay
사랑받지 못함

11
오늘 아침 webapi 2 컨트롤러 방법으로 흥미로운 문제가 발생했습니다 . async void대신 으로 선언되었습니다 async Task. 메소드가 실행을 완료하기 전에 컨트롤러 멤버로 선언 된 Entity Framework 컨텍스트 오브젝트를 사용 중이므로 메소드가 충돌했습니다. 프레임 워크는 메소드 실행이 완료되기 전에 컨트롤러를 배치했습니다. 메서드를 비동기 작업으로 변경했는데 효과가있었습니다.
costa

답변:


417

1) 일반적으로 a를 반환하려고합니다 Task. 이벤트의 경우 리턴 유형 이 필요한 경우는 예외 void입니다. 발신자 await에게 작업 을 허용하지 않을 이유가 없다면 왜 허용하지 않습니까?

2) async리턴 void하는 메소드 는 다른 측면에서 특별합니다. 메소드는 최상위 비동기 조작 을 나타내며 태스크가 예외를 리턴 할 때 적용되는 추가 규칙이 있습니다. 차이점을 보여주는 가장 쉬운 방법은 예제를 사용하는 것입니다.

static async void f()
{
    await h();
}

static async Task g()
{
    await h();
}

static async Task h()
{
    throw new NotImplementedException();
}

private void button1_Click(object sender, EventArgs e)
{
    f();
}

private void button2_Click(object sender, EventArgs e)
{
    g();
}

private void button3_Click(object sender, EventArgs e)
{
    GC.Collect();
}

f예외는 항상 "관측"됩니다. 최상위 비동기 메소드를 떠나는 예외는 처리되지 않은 다른 예외처럼 간단하게 처리됩니다. g의 예외는 관찰되지 않습니다. 가비지 수집기가 작업을 정리하면 해당 작업에서 예외가 발생하여 예외를 처리 한 사람이 없음을 알 수 있습니다. 그렇게되면, TaskScheduler.UnobservedTaskException핸들러가 실행됩니다. 이 일어나게해서는 안됩니다. 예를 사용하려면

public static async void AsyncMethod2(int num)
{
    await Task.Factory.StartNew(() => Thread.Sleep(num));
}

예, 사용 async하고 await여기에, 그들은 예외가 발생하는 경우 반드시 당신의 방법은 여전히 제대로 작동합니다.

자세한 내용은 참조하십시오 : http://msdn.microsoft.com/en-us/magazine/jj991977.aspx


10
나는 의미 f대신 g내 댓글에. 의 예외는 f로 전달됩니다 SynchronizationContext. g인상하지 않습니다 UnobservedTaskException만, UTE이 처리되어 있지 않은 경우 더 이상 과정을 충돌합니다. 이와 같은 "비동기 예외"가 무시 될 수있는 상황이 있습니다.
Stephen Cleary

3
WhenAny여러 개가 있는 경우 Task예외가 발생합니다. 당신은 종종 첫 번째 것만 다루어야하며, 종종 다른 것을 무시하기를 원합니다.
Stephen Cleary

1
@StephenCleary 고마워, 그것은 좋은 예라고 생각합니다 WhenAny. 다른 예외를 무시해도 괜찮은지 여부는 처음에 전화 한 이유에 달려 있습니다. 내가 가지고있는 주요 유스 케이스는 끝날 때 나머지 작업을 기다리고 있습니다. 예외의 유무에 관계없이.

10
void 대신 Task를 반환하는 것이 좋습니다 이유가 조금 혼란 스럽습니다. 당신이 말했듯이 f ()는 던져지고 예외는 있지만 g ()는 그렇지 않습니다. 이러한 백그라운드 스레드 예외를 인식하는 것이 가장 좋지 않습니까?
user981225

2
실제로 @ user981225 그러나 g의 호출자에게는 책임이됩니다. g를 호출하는 모든 메소드는 비동기식이어야하며 대기해야합니다. 이것은 어려운 규칙이 아니라 지침입니다. 특정 프로그램에서 g 반환 무효화가 더 쉽다고 결정할 수 있습니다.

40

나는 Jérôme Laban이 작성 async하고 void작성한 이 매우 유용한 기사를 보았습니다 : https://jaylee.org/archive/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void .html

결론 async+void은 시스템을 중단시킬 수 있으며 일반적으로 UI 측 이벤트 핸들러에서만 사용해야한다는 것입니다.

그 이유는 AsyncVoidMethodBuilder가 사용하는 동기화 컨텍스트이기 때문에이 예제에서는 없습니다. 앰비언트 동기화 컨텍스트가없는 경우 비동기 void 메소드 본문에서 처리되지 않은 예외는 ThreadPool에서 다시 발생합니다. 처리되지 않은 예외가 발생할 수있는 다른 논리적 장소는 없지만, 불행하게도 ThreadPool의 처리되지 않은 예외는 .NET 2.0 이후 프로세스를 효과적으로 종료하기 때문에 프로세스가 종료되는 것입니다. 당신은 AppDomain.UnhandledException 이벤트를 사용하여 모든 처리되지 않은 예외를 가로 챌 수 있지만,이 이벤트에서 프로세스를 복구 할 방법이 없습니다.

UI 이벤트 핸들러를 작성할 때 예외가 비동기가 아닌 메소드에서 발견 된 것과 동일한 방식으로 처리되므로 비동기 void 메소드는 아무 문제가 없습니다. 그들은 디스패처에 던져집니다. 이러한 예외를 복구 할 가능성이 있으며 대부분의 경우 올바른 것 이상입니다. 그러나 UI 이벤트 핸들러 외부에서 비동기 void 메소드는 사용하기에 위험하며 찾기가 쉽지 않을 수 있습니다.


그 맞습니까? 비동기 메서드 내부의 예외가 작업 내에서 캡처되었다고 생각했습니다. 또한 항상 귀찮은 SynchronizationContext가 있습니다
NM

30

나는이 진술에서 분명한 아이디어를 얻었다.

  1. 비동기 void 메소드에는 서로 다른 오류 처리 의미가 있습니다. 비동기 작업 또는 비동기 작업 메서드에서 예외가 발생하면 해당 예외가 캡처되어 Task 개체에 배치됩니다. 비동기 void 메소드에는 Task 객체가 없으므로 비동기 void 메소드에서 발생하는 예외는 SynchronizationContext (SynchronizationContext는 "where"코드가 실행될 수있는 위치를 나타냄)에서 직접 발생합니다. 시작

비동기 공허 메서드의 예외는 캐치로 잡을 수 없습니다

private async void ThrowExceptionAsync()
{
  throw new InvalidOperationException();
}
public void AsyncVoidExceptions_CannotBeCaughtByCatch()
{
  try
  {
    ThrowExceptionAsync();
  }
  catch (Exception)
  {
    // The exception is never caught here!
    throw;
  }
}

이러한 예외는 AppDomain.UnhandledException 또는 GUI / ASP.NET 응용 프로그램에 대한 유사한 catch-all 이벤트를 사용하여 관찰 할 수 있지만 정기적 인 예외 처리를 위해 해당 이벤트를 사용하는 것은 유지 보수를위한 레시피입니다 (응용 프로그램이 충돌 함).

  1. 비동기 void 메소드에는 서로 다른 구성 의미가 있습니다. Task 또는 Task를 반환하는 비동기 메서드는 await, Task.WhenAny, Task.WhenAll 등을 사용하여 쉽게 구성 할 수 있습니다. void를 반환하는 비동기 메서드는 호출 코드가 완료되었음을 알리는 쉬운 방법을 제공하지 않습니다. 여러 개의 비동기 void 메소드를 시작하는 것은 쉽지만 완료 시점을 결정하는 것은 쉽지 않습니다. 비동기 void 메소드는 시작 및 완료시 SynchronizationContext에 알리지 만, 사용자 정의 SynchronizationContext는 일반 애플리케이션 코드를위한 복잡한 솔루션입니다.

  2. 비동기 이벤트 핸들러를 사용할 때 동기 무효화 메소드는 SynchronizationContext에서 직접 예외를 발생시키기 때문에 유용합니다. 이는 동기 이벤트 핸들러가 작동하는 방식과 유사합니다.

자세한 내용은이 링크를 확인을 위해 https://msdn.microsoft.com/en-us/magazine/jj991977.aspx


21

async void를 호출하는 문제는 작업을 다시받지 못하고 기능 작업이 완료된 시점을 알 방법이 없다는 것입니다 ( https://blogs.msdn.microsoft.com/oldnewthing/20170720-00/ ? p = 96655 )

비동기 함수를 호출하는 세 가지 방법은 다음과 같습니다.

async Task<T> SomethingAsync() { ... return t; }
async Task SomethingAsync() { ... }
async void SomethingAsync() { ... }

모든 경우에 함수는 일련의 작업으로 변환됩니다. 차이는 어떤 함수가 반환됩니다.

첫 번째 경우, 함수는 결국 t를 생성하는 작업을 반환합니다.

두 번째 경우, 함수는 제품이없는 태스크를 리턴하지만 완료 될 때까지 알기 위해 계속 기다릴 수 있습니다.

세 번째 사례는 불쾌한 사례입니다. 세 번째 경우는 두 번째 경우와 비슷하지만 작업을 다시받지 못합니다. 함수의 작업이 완료된 시점을 알 방법이 없습니다.

비동기 무효 사례는 "화재와 잊어 버리기"입니다. 작업 체인을 시작하지만 완료 시점에 대해서는 신경 쓰지 않습니다. 함수가 반환되면 처음으로 기다리는 모든 것이 실행되었다는 것입니다. 첫 번째 대기 이후의 모든 항목은 나중에 액세스 할 수없는 지정되지 않은 지점에서 실행됩니다.


6

async void예외를 잡는 데주의를 기울이는 한 백그라운드 작업을 시작 하는 데 사용할 수 있다고 생각합니다 . 생각?

class Program {

    static bool isFinished = false;

    static void Main(string[] args) {

        // Kick off the background operation and don't care about when it completes
        BackgroundWork();

        Console.WriteLine("Press enter when you're ready to stop the background operation.");
        Console.ReadLine();
        isFinished = true;
    }

    // Using async void to kickoff a background operation that nobody wants to be notified about when it completes.
    static async void BackgroundWork() {
        // It's important to catch exceptions so we don't crash the appliation.
        try {
            // This operation will end after ten interations or when the app closes. Whichever happens first.
            for (var count = 1; count <= 10 && !isFinished; count++) {
                await Task.Delay(1000);
                Console.WriteLine($"{count} seconds of work elapsed.");
            }
            Console.WriteLine("Background operation came to an end.");
        } catch (Exception x) {
            Console.WriteLine("Caught exception:");
            Console.WriteLine(x.ToString());
        }
    }
}

1
async void를 호출 할 때의 문제점은 작업을 다시 얻지 못하고 함수 작업이 완료된 시점을 알 방법이 없다는 것입니다.
user8128167

결과에 신경 쓰지 않고 예외가 다른 작업에 영향을 미치지 않도록하는 (드문) 백그라운드 작업에는 적합합니다. 일반적인 사용 사례는 로그 이벤트를 로그 서버로 보내는 것입니다. 백그라운드에서 이런 일이 발생하기를 원하며 로그 서버가 다운 된 경우 서비스 / 요청 핸들러가 실패하지 않도록합니다. 물론, 모든 예외를 잡아야합니다. 그렇지 않으면 프로세스가 종료됩니다. 아니면 이것을 달성하는 더 좋은 방법이 있습니까?
Florian Winter

비동기 무효 메소드의 예외는 캐치로 잡을 수 없습니다. msdn.microsoft.com/ko-kr/magazine/jj991977.aspx
Si Zi

3
@SiZi는 캐치가 예제와 같이 비동기 void 메소드 내부에 있으며 캐치됩니다.
bboyle1234

요구 사항은 당신이 그것을 기록하지 못할 경우에는 일을하지 필요로 변경할 때 무엇에 대해 - 다음 전체 API에 서명을 변경해야합니다, 대신에 단지 현재 예외 무시 //이 논리 변경
Milney

0

내 대답은 간단합니다. void 메소드를 기다릴 수 없습니다.

Error   CS4008  Cannot await 'void' TestAsync   e:\test\TestAsync\TestAsyncProgram.cs

따라서 메소드가 비동기 인 경우 비동기 이점을 잃을 수 있기 때문에 대기하는 것이 좋습니다.


-2

Microsoft 문서에 따르면 절대 사용해서는 안됩니다async void

다음을 수행하지 마십시오. 다음 예제는 async void첫 번째 대기에 도달 할 때 HTTP 요청을 완료하는 데 사용합니다.

  • ASP.NET Core 앱에서는 항상 나쁜 습관입니다.

  • HTTP 요청이 완료된 후 HttpResponse에 액세스합니다.

  • 프로세스가 충돌합니다.

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