기다리지 않고 C #에서 비동기 메서드를 안전하게 호출하는 방법


322

나는이 async데이터를 반환 방법 :

public async Task MyAsyncMethod()
{
    // do some stuff async, don't return any data
}

일부 데이터를 반환하는 다른 메서드에서 이것을 호출합니다.

public string GetStringData()
{
    MyAsyncMethod(); // this generates a warning and swallows exceptions
    return "hello world";
}

MyAsyncMethod()기다리지 않고 호출 하면 Visual Studio에서 " 호출 을 기다리지 않기 때문에 호출이 완료되기 전에 현재 메서드가 계속 실행됩니다 "경고가 발생합니다. 해당 경고 페이지에는 다음이 표시됩니다.

비동기 호출이 완료되기를 기다리지 않고 호출 된 메소드가 예외를 발생시키지 않는 경우에만 경고를 억제하는 것을 고려해야합니다 .

전화가 끝날 때까지 기다리지 않겠다고 확신합니다. 시간이 필요 없거나 시간이 없습니다. 그러나 전화로 예외 가 발생할 수 있습니다 .

나는이 문제에 몇 번 걸려 넘어졌으며 일반적인 해결책이 있어야하는 일반적인 문제라고 확신합니다.

결과를 기다리지 않고 어떻게 비동기 메소드를 안전하게 호출합니까?

최신 정보:

결과를 기다리는 사람들에게 이것은 웹 서비스 (ASP.NET 웹 API)의 웹 요청에 응답하는 코드입니다. UI 컨텍스트에서 대기하면 UI 스레드가 사용 가능한 상태로 유지되지만 웹 요청 호출에서 대기하면 요청에 응답하기 전에 작업이 완료 될 때까지 대기하므로 아무런 이유없이 응답 시간이 늘어납니다.


완료 메소드를 작성하고 무시하는 이유는 무엇입니까? 백그라운드 스레드에서 실행 중이기 때문입니다. 그러면 프로그램 종료가 중단되지 않습니다.
Yahya

1
결과를 기다리지 않으려면 유일한 옵션은 경고를 무시 / 억제하는 것입니다. 당신이 경우 않습니다 다음 결과 / 예외 기다리고 싶지MyAsyncMethod().Wait()
피터 리치에게

1
편집 내용 : 이해가되지 않습니다. 요청 후 1 초 후에 응답이 클라이언트로 전송되고 2 초 후에 비동기 메소드에서 예외가 발생한다고 가정하십시오. 그 예외로 무엇을 하시겠습니까? 응답이 이미 전송 된 경우 클라이언트로 보낼 수 없습니다. 그것으로 무엇을 하시겠습니까?

2
@ 로모 쿠 페어. 어쨌든 누군가가 로그를 본다고 가정합니다. :)

2
ASP.NET 웹 API 시나리오의 변형 은 요청이 오래 걸리는 백그라운드 작업을 생성하지만 비용이 많이 드는 작업을 요청하는 장기 서비스 (예 : Windows 서비스) 의 자체 호스팅 웹 API입니다. HTTP 202 (Accepted)로 신속하게 응답하십시오.
David Rubin

답변:


186

"비동기 적으로"예외를 얻으려면 다음을 수행하십시오.

  MyAsyncMethod().
    ContinueWith(t => Console.WriteLine(t.Exception),
        TaskContinuationOptions.OnlyOnFaulted);

이렇게하면 "메인"스레드 이외의 스레드에서 예외를 처리 할 수 ​​있습니다. 즉, 호출 MyAsyncMethod()하는 스레드에서 호출을 "대기"할 필요가 없습니다 MyAsyncMethod. 그러나 여전히 예외로 무언가를 할 수는 있지만 예외가 발생하는 경우에만 가능합니다.

최신 정보:

기술적으로 다음과 비슷한 작업을 수행 할 수 있습니다 await.

try
{
    await MyAsyncMethod().ConfigureAwait(false);
}
catch (Exception ex)
{
    Trace.WriteLine(ex);
}

... 특별히 사용하는 데 필요한 경우에 유용 할 것이다 try/ catch(또는 using)하지만 난 찾을 ContinueWith당신이 알고 있기 때문에 좀 더 명시 적으로 ConfigureAwait(false)수단을.


7
나는 이것을 확장 메소드로 Task바꿨다. }} 사용법 : MyAsyncMethod (). PerformAsyncTaskWithoutAwait (t => log.ErrorFormat ( "MyAsyncMethod를 호출하는 동안 오류가 발생했습니다 : \ n {0}", t.Exception));
Mark Avenius

2
downvoter. 코멘트? 답변에 잘못된 것이 있으면 알고 알고 수정하고 싶습니다.
피터 리치

2
이봐-난 공감하지 않았지만 ... 업데이트 설명해 줄 수있어? 전화를 기다리지 않아야했기 때문에 전화를 기다리는 경우 전화를 기다리면 캡처 된 상황을 계속하지 않습니다 ...
Bartosz

1
"wait"는이 문맥에서 올바른 설명이 아닙니다. 다음 줄 ConfiguratAwait(false)은 작업이 완료 될 때까지 실행되지 않지만 현재 스레드는 "대기"(예 : 블록)하지 않으며 다음 줄은 대기 호출에 비동기식으로 호출됩니다. ConfigureAwait(false)그 다음 줄이 없으면 원래 웹 요청 컨텍스트에서 실행됩니다. 으로 ConfigurateAwait(false)그것은 계속하기 위해 원본 문장 / 스레드를 확보, 비동기 방식 (작업)과 같은 컨텍스트에서 실행 있어요 ...
피터 리치

15
ContinueWith 버전은 try {await} catch {} 버전과 다릅니다. 첫 번째 버전에서는 ContinueWith () 이후의 모든 것이 즉시 실행됩니다. 초기 작업이 해고되었습니다. 두 번째 버전에서는 catch {} 이후의 모든 것이 초기 작업이 완료된 후에 만 ​​실행됩니다. 두 번째 버전은 "await MyAsyncMethod (). ContinueWith (t => Console.WriteLine (t.Exception), TaskContinuationOptions.OnlyOnFaulted) .ConfigureAwait (fals);
Thanasis Ioannidis

67

먼저 만드는 것이 좋습니다 방법과이 에서 반환 된 작업 .GetStringDataasyncawaitMyAsyncMethod

예외를 처리 할 필요가 MyAsyncMethod 없거나 완료 시점을 알 필요가없는 경우 다음을 수행 할 수 있습니다.

public string GetStringData()
{
  var _ = MyAsyncMethod();
  return "hello world";
}

BTW, 이것은 "일반적인 문제"가 아닙니다. 그것은 몇 가지 코드를 실행하고 완료 여부를 상관하지 싶어 매우 희귀 하고 성공적으로 완료 여부를 상관하지.

최신 정보:

ASP.NET을 사용하고 있고 일찍 돌아가고 싶기 때문에 주제에 대한블로그 게시물이 유용하다는 것을 알 수 있습니다. 그러나 ASP.NET은이를 위해 설계된 것이 아니며 응답이 반환 된 후에 코드가 실행된다는 보장 이 없습니다 . ASP.NET은 실행되도록 최선을 다하지만 보장 할 수는 없습니다.

따라서 이것은 이벤트를 로그로 던져서 여기저기서 몇 가지를 잃어도 실제로 중요 하지 않은 간단한 방법에 대한 훌륭한 솔루션입니다 . 모든 종류의 비즈니스 크리티컬 운영에 적합한 솔루션은 아닙니다. 이러한 상황에서는 작업 (예 : Azure Queues, MSMQ)을 저장하고 별도의 백그라운드 프로세스 (예 : Azure Worker Role, Win32 Service)를 저장하는 지속적인 방법으로보다 복잡한 아키텍처를 채택 해야 합니다.


2
당신이 오해 한 것 같아요 내가 이 예외를 던져 실패 할 경우주의,하지만 난 내 데이터를 반환하기 전에 방법을 기다리고 싶지 않아요. 또한 차이가 나는 경우 작업중 인 컨텍스트에 대한 편집 내용을 참조하십시오.
George Powell

5
@GeorgePowell : 활성 요청없이 ASP.NET 컨텍스트에서 코드를 실행하는 것은 매우 위험합니다. 나는 당신을 도울 수 있는 블로그 게시물을 가지고 있지만 문제에 대해 더 많이 알지 못하면 그 접근법을 추천할지 여부를 말할 수 없습니다.
Stephen Cleary

@StephenCleary 비슷한 요구가 있습니다. 내 예에서는 클라우드에서 실행할 일괄 처리 엔진이 필요합니다. 일괄 처리를 시작하기 위해 끝점을 "핑"하지만 즉시 돌아오고 싶습니다. 핑을 시작하면 시작되므로 모든 것을 처리 할 수 ​​있습니다. 예외가 발생하면, 그냥 "BatchProcessLog / Error"테이블에 기록 될 것입니다.
ganders

8
C # 7, 당신은 대체 할 수 var _ = MyAsyncMethod();와 함께 _ = MyAsyncMethod();. 그래도 CS4014 경고는 피할 수 있지만 변수를 사용하지 않는 것이 좀 더 분명해집니다.
Brian

48

Peter Ritchie의 대답은 내가 원했던 것이 었으며 ASP.NET에서 일찍 돌아 오는 것에 대한 Stephen Cleary의 기사 는 매우 도움이되었습니다.

그러나보다 일반적인 문제 (ASP.NET 컨텍스트에만 해당되지 않음) 다음 콘솔 응용 프로그램은 다음을 사용하여 Peter의 답변 사용 및 동작을 보여줍니다. Task.ContinueWith(...)

static void Main(string[] args)
{
  try
  {
    // output "hello world" as method returns early
    Console.WriteLine(GetStringData());
  }
  catch
  {
    // Exception is NOT caught here
  }
  Console.ReadLine();
}

public static string GetStringData()
{
  MyAsyncMethod().ContinueWith(OnMyAsyncMethodFailed, TaskContinuationOptions.OnlyOnFaulted);
  return "hello world";
}

public static async Task MyAsyncMethod()
{
  await Task.Run(() => { throw new Exception("thrown on background thread"); });
}

public static void OnMyAsyncMethodFailed(Task task)
{
  Exception ex = task.Exception;
  // Deal with exceptions here however you want
}

GetStringData()초기 기다리고없이 반환 MyAsyncMethod()에 던져 예외 MyAsyncMethod()로 처리되어 OnMyAsyncMethodFailed(Task task)하지 에서 try/ catch주변GetStringData()


9
Console.ReadLine();약간의 수면 / 지연을 제거 하고 추가 MyAsyncMethod하면 예외가 발생하지 않습니다.
tymtam

17

이 솔루션으로 끝납니다.

public async Task MyAsyncMethod()
{
    // do some stuff async, don't return any data
}

public string GetStringData()
{
    // Run async, no warning, exception are catched
    RunAsync(MyAsyncMethod()); 
    return "hello world";
}

private void RunAsync(Task task)
{
    task.ContinueWith(t =>
    {
        ILog log = ServiceLocator.Current.GetInstance<ILog>();
        log.Error("Unexpected Error", t.Exception);

    }, TaskContinuationOptions.OnlyOnFaulted);
}


2

질문이 생겼을 것 같아 왜 그렇게해야합니까? 대한 이유asyncC # 5.0 는 결과를 기다릴 수 있기 때문입니다. 이 메소드는 실제로 비동기식이 아니라 현재 스레드를 너무 많이 방해하지 않도록 한 번에 간단히 호출됩니다.

아마도 스레드를 시작하고 그대로 두는 것이 좋습니다.


2
async결과를 "대기"하는 것 이상입니다. "await"는 "await"다음의 행이 "await"를 호출 한 동일한 스레드에서 비동기 적으로 실행됨을 의미합니다. 이것은 사용에 능력뿐만 아니라 (물론, "await를"없이 할,하지만 당신은 대의원의 무리를 가지고 결국 및 코드의 순차적 인 모양과 느낌을 잃을 수 있습니다 usingtry/catch...
피터 리치

1
@PeterRitchie 기능적으로 비동기적이고 await키워드를 사용하지 않고 키워드를 사용하지 않는 메소드를 가질 수 async있지만 해당 메소드의 정의에 async사용하지 않고 키워드 를 사용하는 데는 아무런 의미가 없습니다 await.
Servy

1
@PeterRitchie "성명 async은"결과를 기다리는 것 "이상의 의미가 있습니다." async키워드 (당신이 역 따옴표에서 그것을 둘러싸 키워드의 암시는) 의미 아무것도 더 많은 결과를 기다리는 것보다. 일반적인 CS 개념과 마찬가지로 비동기식이기 때문에 결과를 기다리는 것 이상의 의미가 있습니다.
Servy

1
@Servy async는 비동기 메소드 내에서 대기 를 관리하는 상태 머신을 작성합니다. await메소드 내에 s 가 없으면 여전히 해당 상태 머신을 작성하지만 메소드는 비동기식이 아닙니다. 그리고 async메소드가을 반환 void하면 기다릴 필요가 없습니다. 그래서, 그건 다만 결과를 기다리는 것보다.
피터 리치

1
@PeterRitchie 메소드가 태스크를 리턴하는 한 대기 할 수 있습니다. 상태 머신이나 async키워드 가 필요하지 않습니다 . 당신이해야 할 일은 (그리고 특별한 경우에 상태 머신과 함께 결국 발생하는 모든 것) 메소드가 동기식으로 실행되고 완료된 작업에 래핑됩니다. 기술적으로 상태 머신을 제거하는 것이 아니라고 가정합니다. 상태 머신을 제거한 다음을 호출 Task.FromResult합니다. 나는 당신 (그리고 컴파일러 작가)이 독자적으로 부록을 추가 할 수 있다고 가정했다.
Servy

0

메시지 루프가있는 기술 (ASP가 그 중 하나인지 확실하지 않은 기술)에서는 작업이 끝날 때까지 루프를 차단하고 메시지를 처리하고 ContinueWith를 사용하여 코드를 차단 해제 할 수 있습니다.

public void WaitForTask(Task task)
{
    DispatcherFrame frame = new DispatcherFrame();
    task.ContinueWith(t => frame.Continue = false));
    Dispatcher.PushFrame(frame);
}

이 방법은 ShowDialog를 차단하고 UI를 계속 반응시키는 것과 유사합니다.


0

나는 파티에 늦었지만 다른 답변에서 참조하지 않은 멋진 라이브러리가 있습니다.

https://github.com/brminnick/AsyncAwaitBestPractices

"Fire And Forget"이 필요한 경우 작업에서 확장 메서드를 호출합니다.

onException 액션을 호출에 전달하면 예외를 우아한 방식으로 처리 할 수있는 능력을 유지하면서 실행을 기다릴 필요가없고 사용자의 속도를 늦출 필요가 없습니다.

귀하의 예에서는 다음과 같이 사용합니다.

   public string GetStringData()
    {
        MyAsyncMethod().SafeFireAndForget(onException: (exception) =>
                    {
                      //DO STUFF WITH THE EXCEPTION                    
                    }); 
        return "hello world";
    }

또한 MVVM Xamarin 솔루션에 적합한 ICommand를 구현하는 AsyncCommands를 기다립니다.


-1

해결책은 sincronization 컨텍스트없이 HttpClient를 다른 실행 작업으로 시작하는 것입니다.

var submit = httpClient.PostAsync(uri, new StringContent(body, Encoding.UTF8,"application/json"));
var t = Task.Run(() => submit.ConfigureAwait(false));
await t.ConfigureAwait(false);

-1

당신이 정말로 이것을 원한다면. "대기없이 C #에서 비동기 메서드 호출"을 해결하기 위해 a 내부에서 async 메서드를 실행할 수 있습니다 Task.Run. 이 방법은 끝날 때까지 기다립니다 MyAsyncMethod.

public string GetStringData()
{
    Task.Run(()=> MyAsyncMethod()).Result;
    return "hello world";
}

await비동기 적으로 Result작업의 랩을 해제 하지만 결과를 사용 하면 작업이 완료 될 때까지 차단됩니다.


-1

일반적으로 비동기 메서드는 Task 클래스를 반환합니다. Wait()메소드 또는 Result속성 을 사용 하고 코드에서 예외가 발생하면 (예외 유형이 래핑 됨) 올바른 예외를 찾으 AggregateException려면 쿼리해야합니다 Exception.InnerException.

그러나 .GetAwaiter().GetResult()대신 사용할 수도 있습니다. 비동기 작업도 대기하지만 예외는 줄지 않습니다.

여기 짧은 예가 있습니다 :

public async Task MyMethodAsync()
{
}

public string GetStringData()
{
    MyMethodAsync().GetAwaiter().GetResult();
    return "test";
}

비동기 함수에서 일부 매개 변수를 반환 할 수도 있습니다. Action<return type>예를 들어 다음과 같이 비동기 함수에 추가 를 제공 하면됩니다.

public string GetStringData()
{
    return MyMethodWithReturnParameterAsync().GetAwaiter().GetResult();
}

public async Task<String> MyMethodWithReturnParameterAsync()
{
    return "test";
}

비동기 메서드는 ASync이름이 같은 동기화 함수 간의 충돌을 피할 수 있도록 일반적으로 접미사 이름을 갖습니다 . (예 FileStream.ReadAsync)-이 권장 사항에 따라 기능 이름을 업데이트했습니다.

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