"경고 CS4014 :이 호출을 기다리지 않기 때문에 현재 메소드의 실행이 계속됩니다 ..."억제


156

이것은 "대기없이 C #에서 비동기 메서드를 안전하게 호출하는 방법" 과 중복되지 않습니다 .

다음 경고를 어떻게 잘 억제합니까?

경고 CS4014 :이 호출을 기다리지 않으므로 호출이 완료되기 전에 현재 메소드의 실행이 계속됩니다. 'await'연산자를 호출 결과에 적용하십시오.

간단한 예 :

static async Task WorkAsync()
{
    await Task.Delay(1000);
    Console.WriteLine("Done!");
}

static async Task StartWorkAsync()
{
    WorkAsync(); // I want fire-and-forget 

    // more unrelated async/await stuff here, e.g.:
    // ...
    await Task.Delay(2000); 
}

내가 시도하고 싫어했던 것 :

static async Task StartWorkAsync()
{
    #pragma warning disable 4014
    WorkAsync(); // I want fire-and-forget here
    #pragma warning restore 4014
    // ...
}

static async Task StartWorkAsync()
{
    var ignoreMe = WorkAsync(); // I want fire-and-forget here
    // ...
}

업데이트 때문에, 원래의 허용 대답은 편집 된, 나는에 허용 대답을 변경 한 C 7.0 폐기 # 사용하여 하나의 내가 생각하지 않는 한, ContinueWith여기에 적합하다. 화재 및 잊어 버린 작업에 대한 예외를 기록해야 할 때마다 Stephen Cleary가 제안한 보다 정교한 접근 방식을 사용합니다 .


1
당신 #pragma은 좋지 않다고 생각 합니까?
Frédéric Hamidi

10
@ 프레데릭 하 미디
noseratio

2
@Noseratio : 아, 맞습니다. 죄송합니다. 다른 경고 인 것 같습니다. 나를 무시하십시오!
Jon Skeet

3
@ Terribad : 나는 확실하지 않습니다-대부분의 경우 경고가 꽤 합리적인 것 같습니다. 특히, 당신은 어떤 실패에 어떤 일이 일어나고 싶은가에 대해 생각해야합니다. 보통 "불과 잊어 버리기"에 대해서도 실패를 기록하는 방법을 알아 내야합니다.
Jon Skeet

4
@Terribad,이 방법이나 다른 방법으로 사용하기 전에 비동기 메소드에 대해 예외가 어떻게 전파되는지에 대한 명확한 그림이 있어야합니다 ( 이것을 확인 하십시오 ). 그런 다음 @ Knaģ의 답변 은 도우미 방법을 통해 화재 및 잊어 버림에 대한 예외를 잃지 않는 우아한 방법을 제공합니다 async void.
noseratio

답변:


160

C # 7에서는 이제 폐기를 사용할 수 있습니다 .

_ = WorkAsync();

7
이것은 내가 기억할 수없는 편리한 작은 언어 기능입니다. _ = ...내 두뇌에 있는 것과 같습니다 .
Marc L.

3
SupressMessage가 Visual Studio "오류 목록"에서 내 경고를 제거했지만 "출력" #pragma warning disable CSxxxx이 아니라 폐기보다 더보기 흉한 것으로 나타났습니다 .)
David Savage

122

경고를 방지하는 확장 방법을 만들 수 있습니다. 확장 메소드는 비어 있거나 예외 처리를 추가 할 수 있습니다 .ContinueWith().

static class TaskExtensions
{
    public static void Forget(this Task task)
    {
        task.ContinueWith(
            t => { WriteLog(t.Exception); },
            TaskContinuationOptions.OnlyOnFaulted);
    }
}

public async Task StartWorkAsync()
{
    this.WorkAsync().Forget();
}

그러나 ASP.NET 은 실행중인 작업 수를 계산하므로 Forget()위에 나열된 간단한 확장명 에서는 작동하지 않으며 예외로 인해 실패 할 수 있습니다.

비동기 작업이 여전히 보류중인 동안 비동기 모듈 또는 핸들러가 완료되었습니다.

.NET 4.5.2에서는 HostingEnvironment.QueueBackgroundWorkItem다음 을 사용하여 해결할 수 있습니다 .

public static Task HandleFault(this Task task, CancellationToken cancelToken)
{
    return task.ContinueWith(
        t => { WriteLog(t.Exception); },
        cancelToken,
        TaskContinuationOptions.OnlyOnFaulted,
        TaskScheduler.Default);
}

public async Task StartWorkAsync()
{
    System.Web.Hosting.HostingEnvironment.QueueBackgroundWorkItem(
        cancelToken => this.WorkAsync().HandleFault(cancelToken));
}

8
찾았습니다 TplExtensions.Forget. 아래에 더 많은 장점이 Microsoft.VisualStudio.Threading있습니다. Visual Studio SDK 외부에서 사용할 수 있기를 바랍니다.
noseratio

1
@ Noseratio와 Knagis, 나는이 접근법을 좋아하고 그것을 사용할 계획입니다. 관련 후속 질문을 게시했습니다. stackoverflow.com/questions/22864367/fire-and-forget-approach
Matt Smith

3
@stricq Forget ()에 ConfigureAwait (false)를 추가하면 어떤 목적으로 사용됩니까? 내가 알기로, ConfigureAwait는 작업에서 await가 사용되는 지점에서만 스레드 동기화에 영향을 주지만 Forget ()의 목적은 작업을 버리는 것이므로 작업을 절대 기다릴 수 없으므로 ConfigureAwait는 의미가 없습니다.
dthorpe

3
ConfigureAwait (false)없이 실행 및 잊어 버리기 작업이 완료되기 전에 생성 스레드가 사라지면 여전히 생성 스레드로 다시 마샬링하려고 시도하지만 해당 스레드는 교착 상태가됩니다. ConfigureAwait (false)를 설정하면 시스템이 호출 스레드에 마샬링하지 않도록 지시합니다.
stricq

2
이 답변에는 특정 사례를 관리하기위한 수정 사항과 수십 개의 댓글이 있습니다. 단순히 일이 종종 옳은 일이며, 버려야합니다! 그리고 @ fjch1997 답변을 인용하십시오 : 경고를 억제하기 위해 몇 가지 틱을 실행하는 메소드를 만드는 것은 어리 석습니다.
Teejay

39

다음 속성을 사용하여 메소드를 장식 할 수 있습니다.

[System.Diagnostics.CodeAnalysis.SuppressMessage("Await.Warning", "CS4014:Await.Warning")]
static async Task StartWorkAsync()
{
    WorkAsync();
    // ...
}

기본적으로 컴파일러는 수행중인 작업을 알고 있으며 가능한 실수에 대해 걱정할 필요가 없다는 것을 컴파일러에 알리고 있습니다.

이 코드의 중요한 부분은 두 번째 매개 변수입니다. "CS4014 :"부분은 경고를 억제하는 것입니다. 당신은 나머지에 원하는 것을 쓸 수 있습니다.


작동하지 않습니다 : Visual Studio for Mac 7.0.1 (빌드 24). 그럴 것 같지만-아니.
IronRod

1
[SuppressMessage("Compiler", "CS4014")]오류 목록 창에서 메시지를 표시하지 않지만 출력 창에 여전히 경고 줄이
David Ching

35

이것을 다루는 두 가지 방법.

삭제 변수 (C # 7)에 저장

_ = Task.Run(() => DoMyStuff()).ConfigureAwait(false);

C # 7에서 폐기가 도입되었으므로 경고를 억제하는 것보다 낫다고 생각합니다. 경고를 억제 할뿐만 아니라 화재와 잊어 버린 의도를 분명하게하기 때문입니다.

또한 컴파일러는 릴리스 모드에서 최적화 할 수 있습니다.

그냥 억제

#pragma warning disable 4014
...
#pragma warning restore 4014

"화재하고 잊어 버리기"에 충분한 솔루션입니다

이 경고가 존재하는 이유는 많은 경우 태스크를 기다리지 않고 태스크를 리턴하는 메소드를 사용하려는 의도가 아니기 때문입니다. 발사하고 잊어 버릴 때 경고를 억제하는 것이 좋습니다.

철자법을 기억하는 데 어려움이 있다면 #pragma warning disable 4014Visual Studio에서 추가하십시오. Ctrl +를 누르십시오. "Quick Actions"를 연 다음 "Suppress CS2014"를 엽니 다.

전부

경고를 억제하기 위해 몇 번의 틱을 실행하는 메소드를 작성하는 것은 어리 석습니다.


이것은 Visual Studio for Mac 7.0.1 (빌드 24)에서 작동했습니다.
IronRod

1
경고를 억제 할 목적으로 실행하는 데 몇 가지 틱이 더 필요한 메소드를 작성하는 것은 어리석은 일입니다. 이 틱은 전혀 틱을 추가하지 않으며 IMO는 더 읽기 [MethodImpl(MethodImplOptions.AggressiveInlining)] void Forget(this Task @this) { } /* ... */ obj.WorkAsync().Forget();
쉽습니다

1
@Noseratio AggressiveInlining컴파일러를 사용할 때 여러 번 이유를 불문하고 무시한다
fjch1997

1
pragma 옵션은 매우 간단하며 전체 방법이 아니라 현재 코드 행에만 적용되기 때문에 pragma 옵션이 마음에 듭니다.
wasatchwizard

2
와 같은 오류 코드를 사용하고 #pragma warning disable 4014이후에 경고를 복원하는 것을 잊지 마십시오 #pragma warning restore 4014. 여전히 오류 코드없이 작동하지만 오류 번호를 추가하지 않으면 모든 메시지가 표시되지 않습니다.
DunningKrugerEffect

11

경고를 중지하는 쉬운 방법은 작업을 호출 할 때 간단히 작업을 할당하는 것입니다.

Task fireAndForget = WorkAsync(); // No warning now

그리고 원래 게시물에서 다음을 수행합니다.

static async Task StartWorkAsync()
{
    // Fire and forget
    var fireAndForget = WorkAsync(); // Tell the compiler you know it's a task that's being returned 

    // more unrelated async/await stuff here, e.g.:
    // ...
    await Task.Delay(2000); 
}

나는 특히 마음에 들지 않는 질문 중 하나로서 질문 자체 에서이 접근법을 언급했습니다.
noseratio

으악! 그것이 당신의 pragma와 같은 코드 섹션에 있었기 때문에 눈치 채지 못했습니다 ... 그리고 나는 답을 찾고있었습니다. 그 외에도이 방법에 대해 싫어하는 점은 무엇입니까?
noelicus

1
task잊혀진 지역 변수처럼 보이지 않습니다 . 컴파일러가 나에게 또 다른 경고를 주어야하는 것처럼, " task할당되지 않지만 그 값은 사용되지 않습니다"와 같은 것이 있습니다. 또한 코드를 읽기 어렵게 만듭니다. 나는 나 자신 이이 접근법을 사용 한다 .
noseratio

충분히 공평합니다-나는 비슷한 느낌을 받았기 때문에 그 이름을지었습니다 fireAndForget... 그래서 나중에 언급되지 않을 것으로 기대합니다.
noelicus

4

경고의 이유는 WorkAsync가 Task읽거나 기다리지 않은 것을 반환하기 때문입니다. WorkAsync의 반환 유형을로 설정하면 void경고가 사라집니다.

일반적으로 Task호출자가 작업자의 상태를 알아야 할 때 메소드가를 반환합니다 . fire-and-forget의 경우, 호출자가 호출 된 메소드와 독립적임을 나타 내기 위해 void를 리턴해야합니다.

static async void WorkAsync()
{
    await Task.Delay(1000);
    Console.WriteLine("Done!");
}

static async Task StartWorkAsync()
{
    WorkAsync(); // no warning since return type is void

    // more unrelated async/await stuff here, e.g.:
    // ...
    await Task.Delay(2000); 
}

2

void를 반환하는 비동기 메서드 안에 래핑하지 않겠습니까? 약간 길지만 모든 변수가 사용됩니다.

static async Task StartWorkAsync()
{   
     async void WorkAndForgetAsync() => await WorkAsync();
     WorkAndForgetAsync(); // no warning
}

1

오늘 우연히이 접근법을 발견했습니다. 대리자를 정의하고 먼저 비동기 메서드를 대리자에게 할당 할 수 있습니다.

    delegate Task IntermediateHandler();



    static async Task AsyncOperation()
    {
        await Task.Yield();
    }

그렇게 부르세요

(new IntermediateHandler(AsyncOperation))();

...

델리게이트를 사용할 때 컴파일러가 똑같은 경고를하지 않는 것이 흥미 롭습니다.


델리게이트를 선언 할 필요가 없습니다. (new Func<Task>(AsyncOperation))()IMO가 여전히 너무 장황하지만 그렇게 할 수도 있습니다 .
noseratio
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.