이 호출이 기다리고 있지 않다는 경고, 현재 메소드의 실행은 계속


135

VS2012를 얻었고에 대한 핸들을 얻으려고했습니다 async.

차단 소스에서 일부 값을 가져 오는 메소드가 있다고 가정 해 봅시다. 메소드 호출자가 차단하고 싶지 않습니다. 값이 도착하면 호출되는 콜백을 수행하는 메소드를 작성할 수 있지만 C # 5를 사용하고 있으므로 호출자가 콜백을 처리하지 않아도되도록 메소드를 비동기로 설정하기로 결정합니다.

// contrived example (edited in response to Servy's comment)
public static Task<string> PromptForStringAsync(string prompt)
{
    return Task.Factory.StartNew(() => {
        Console.Write(prompt);
        return Console.ReadLine();
    });
}

다음은이를 호출하는 예제 메소드입니다. 경우 PromptForStringAsync비동기 아니었다,이 방법은 콜백 내에서 콜백을 중첩 필요합니다. 비동기를 사용하면이 방법을 매우 자연스럽게 작성할 수 있습니다.

public static async Task GetNameAsync()
{
    string firstname = await PromptForStringAsync("Enter your first name: ");
    Console.WriteLine("Welcome {0}.", firstname);

    string lastname = await PromptForStringAsync("Enter your last name: ");
    Console.WriteLine("Name saved as '{0} {1}'.", firstname, lastname);
}

여태까지는 그런대로 잘됐다. 문제는 GetNameAsync를 호출 할 때입니다 .

public static void DoStuff()
{
    GetNameAsync();
    MainWorkOfApplicationIDontWantBlocked();
}

요점은 GetNameAsync비동기 적이라는 것입니다. MainWorkOfApplicationIDontWantBlocked ASAP로 돌아가서 GetNameAsync가 백그라운드에서 작동하도록하기 때문에 차단 하고 싶지 않습니다 . 그러나이 방법으로 호출하면 컴파일러 경고가 표시됩니다 GetNameAsync.

Warning 1   Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

"통화가 완료되기 전에 현재 메소드의 실행이 계속된다"는 것을 완벽하게 알고 있습니다. 그것이 비동기 코드 의 요점 입니다. 맞습니까?

경고없이 컴파일하는 코드를 선호하지만, 코드가 의도 한대로 정확하게 수행하므로 여기에서 "수정"할 것은 없습니다. 반환 값을 저장하여 경고를 제거 할 수 있습니다 GetNameAsync.

public static void DoStuff()
{
    var result = GetNameAsync(); // supress warning
    MainWorkOfApplicationIDontWantBlocked();
}

그러나 이제 불필요한 코드가 있습니다. Visual Studio는 정상적인 "사용하지 않은 값"경고를 표시하지 않기 때문에이 불필요한 코드를 작성해야한다는 것을 이해하고있는 것 같습니다.

비동기가 아닌 메서드에 GetNameAsync를 래핑하여 경고를 제거 할 수도 있습니다.

    public static Task GetNameWrapper()
    {
        return GetNameAsync();
    }

그러나 그것은 훨씬 불필요한 코드입니다. 따라서 불필요한 경고가 필요하지 않거나 허용하지 않는 코드를 작성해야합니다.

여기에 잘못된 비동기 사용에 관한 것이 있습니까?


2
BTW, 구현할 때 PromptForStringAsync필요한 것보다 많은 작업을 수행합니다. 의 결과를 반환하십시오 Task.Factory.StartNew. 콘솔에 입력 된 문자열이 가치있는 작업입니다. 결과를 기다릴 필요가 없습니다. 그렇게하면 새로운 가치가 없습니다.
Servy

더 이해가 Wwouldn't에 대한 GetNameAsync예 (사용자가 제공 한 이름 제공 Task<Name>, 오히려 단지를 돌아보다 Task? DoStuff그 작업을 저장할 수 있고, 하나 await 다른 방법, 또는 그 다른 작업을 통과를 방법 그래서 수 await또는 Wait내부 그것의 구현의 어딘가에.
Servy

@Servy : Task 만 반환하면 "이 메서드는 비동기 메서드이므로 반환 식은 'Task <string>'이 아닌 'string'형식이어야합니다."라는 오류가 발생합니다.
Mud

1
async키워드를 삭제하십시오 .
Servy

13
IMO, 이것은 C # 팀의 경고에 대한 잘못된 선택이었습니다. 경고는 거의 확실하게 잘못된 것이어야합니다. 비동기 메소드를 "실행 및 잊어 버리려는"경우가 많으며 실제로 대기하는 경우가 많습니다.
MgSam

답변:


103

실제로 결과가 필요하지 않으면 GetNameAsync의 서명을 변경하여 void다음 을 반환 할 수 있습니다 .

public static async void GetNameAsync()
{
    ...
}

관련 질문에 대한 답변을 참조하십시오 : 보이드 반환과 작업 반환의 차이점은 무엇입니까?

최신 정보

결과가 필요 GetNameAsync하면를 반환하도록 변경할 수 있습니다 Task<string>.

public static async Task<string> GetNameAsync()
{
    string firstname = await PromptForStringAsync("Enter your first name: ");
    string lastname = await PromptForStringAsync("Enter your last name: ");
    return firstname + lastname;
}

다음과 같이 사용하십시오.

public static void DoStuff()
{
    Task<string> task = GetNameAsync();

    // Set up a continuation BEFORE MainWorkOfApplicationIDontWantBlocked
    Task anotherTask = task.ContinueWith(r => {
            Console.WriteLine(r.Result);
        });

    MainWorkOfApplicationIDontWantBlocked();

    // OR wait for the result AFTER
    string result = task.Result;
}

15
한 번만 결과를 필요로하지 않는 것과는 달리 결과에 신경 쓰지 않는 경우에만 해당 됩니다 .
Servy

3
@Servy, 설명을 해주셔서 감사합니다. 그러나 OP GetNameAsync는 값을 반환하지 않습니다 (물론 결과 자체는 제외).
Nikolay Khil

2
맞지만 작업을 반환하면 모든 비동기 작업 실행이 완료된 시점을 알 수 있습니다. 이 반환 void되면 언제 완료되었는지 알 수 없습니다. 이것이 제가 이전 의견에서 "결과"라고 말했을 때의 의미입니다.
Servy

29
일반적으로 async void이벤트 핸들러를 제외하고 는 메소드 가 없어야합니다 .
Daniel Mann

2
여기서주의해야 할 사항은 async void포착하지 않은 예외로 전환 할 때 관찰되지 않은 예외가 발생하면 프로세스가 충돌하지만 .net 4.5에서는 계속 실행 된다는 점에서 동작이 다르다는 것입니다.
Caleb Vear 5

62

이 토론에 꽤 늦었지만 #pragma전 처리기 지시문 을 사용하는 옵션도 있습니다 . 여기에 일부 조건에서 명시 적으로 기다리고 싶지 않은 비동기 코드가 있으며 나머지 사람들처럼 경고 및 사용하지 않는 변수를 싫어합니다.

#pragma warning disable 4014
SomeMethodAsync();
#pragma warning restore 4014

"4014"이 MSDN 페이지에서 제공 : 컴파일러 경고 (수준 1) CS4014 .

https://stackoverflow.com/a/12145047/928483에서 @ ryan-horath의 경고 / 응답도 참조 하십시오 .

대기하지 않는 비동기 호출 중에 발생한 예외는 유실됩니다. 이 경고를 없애려면 비동기 호출의 작업 반환 값을 변수에 할당해야합니다. 이렇게하면 throw 된 예외에 액세스 할 수 있으며 이는 반환 값에 표시됩니다.

C # 7.0 업데이트

C # 7.0은 새로운 기능인 변수 삭제 : Discards-C # Guide를 추가하여 이와 관련하여 도움을 줄 수 있습니다.

_ = SomeMethodAsync();

5
정말 대단합니다. 나는 당신이 이것을 할 수있을 줄 몰랐습니다. 감사합니다!
Maxim Gershkovich

1
그것도 말을 할 필요는 없습니다 var만 쓰기,_ = SomeMethodAsync();
레이

41

나는 사용하지 않는 변수에 작업을 할당하거나 void를 반환하도록 메소드 서명을 변경하는 솔루션을 좋아하지 않습니다. 전자는 불필요한 비 직관적 인 코드를 생성하지만 후자는 인터페이스를 구현하거나 반환 된 작업을 사용하려는 함수를 다시 사용하는 경우 가능하지 않을 수 있습니다.

내 해결책은 아무것도하지 않는 DoNotAwait ()라는 Task의 확장 메서드를 만드는 것입니다. 이렇게하면 ReSharper 등의 모든 경고가 표시되지 않을뿐만 아니라 코드를보다 이해하기 쉽게하고 코드의 미래 관리자에게 전화를 기다리지 않을 것이라는 사실을 알려줍니다.

확장 방법 :

public static class TaskExtensions
{
    public static void DoNotAwait(this Task task) { }
}

용법:

public static void DoStuff()
{
    GetNameAsync().DoNotAwait();
    MainWorkOfApplicationIDontWantBlocked();
}

추가하기 위해 편집 : 이것은 확장 방법이 아직 시작되지 않은 경우 작업을 시작하는 Jonathan Allen의 솔루션과 유사하지만 발신자의 의도가 완전히 명확하도록 단일 목적 기능을 선호합니다.


1
나는 이것을 좋아하지만 이름을 Unawait ();)로 변경했습니다.
Ostati

29

async void 나쁘다!

  1. 무효 반환과 작업 반환의 차이점은 무엇입니까?
  2. https://jaylee.org/archive/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void.html

내가 제안 Task하는 것은 익명의 방법을 통해 명시 적으로 실행하는 것입니다 ...

예 :

public static void DoStuff()
{
    Task.Run(async () => GetNameAsync());
    MainWorkOfApplicationIDontWantBlocked();
}

또는 차단을 원하면 익명 메소드를 기다릴 수 있습니다.

public static void DoStuff()
{
    Task.Run(async () => await GetNameAsync());
    MainWorkOfApplicationThatWillBeBlocked();
}

그러나 GetNameAsync메소드가 UI 또는 UI 바운드와 상호 작용 해야하는 경우 (WINRT / MVVM, 당신을보고 있습니다) 약간 더 재미있어집니다 =)

UI 디스패처에 대한 참조를 다음과 같이 전달해야합니다.

Task.Run(async () => await GetNameAsync(CoreApplication.MainView.CoreWindow.Dispatcher));

그런 다음 비동기 메소드에서 디스패처가 생각한 UI 또는 UI 바운드 요소와 상호 작용해야합니다 ...

dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => {  this.UserName = userName; });

귀하의 작업 확장은 훌륭합니다. 내가 왜 구현하지 않은지 모르겠다.
Mikael Dúi Bolinder

8
언급 한 첫 번째 접근 방식은 다른 경고 This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread. 를 발생시킵니다 . 이로 인해 새 스레드가 생성되는 반면 async / await만으로 새 스레드가 생성되는 것은 아닙니다.
gregsdennis

당신은 일어나야합니다!
Ozkan

16

이것이 내가 현재하고있는 일입니다.

SomeAyncFunction().RunConcurrently();

경우 RunConcurrently로 정의된다 ...

 /// <summary> 
 /// Runs the Task in a concurrent thread without waiting for it to complete. This will start the task if it is not already running. 
 /// </summary> 
 /// <param name="task">The task to run.</param> 
 /// <remarks>This is usually used to avoid warning messages about not waiting for the task to complete.</remarks> 
 public static void RunConcurrently(this Task task) 
 { 
     if (task == null) 
         throw new ArgumentNullException("task", "task is null."); 

     if (task.Status == TaskStatus.Created) 
         task.Start(); 
 } 

https://github.com/docevaad/Anchor/blob/master/Tortuga.Anchor/Tortuga.Anchor.source/shared/TaskUtilities.cs

https://www.nuget.org/packages/Tortuga.Anchor/


1
+1. 이것은 다른 답변의 의견에서 모든 문제가 비교되는 것을 피하는 것처럼 보입니다. 감사.
Grault

3
당신은 이것을 필요로합니다 : public static void Forget(this Task task) { }
Shital Shah

1
@ShitalShah
Jay Wick 님

@ShitalShah는로 만든 것과 같은 자동 시작 작업에서만 작동합니다 async Task. 일부 작업은 수동으로 시작해야합니다.
Jonathan Allen

7

이 경고에 대한 Microsoft 기사에 따르면 반환 된 작업을 변수에 지정하면 해결할 수 있습니다. 아래는 Microsoft 예제에서 제공된 코드의 번역입니다.

    // To suppress the warning without awaiting, you can assign the 
    // returned task to a variable. The assignment doesn't change how
    // the program runs. However, the recommended practice is always to
    // await a call to an async method.
    // Replace Call #1 with the following line.
    Task delayTask = CalledMethodAsync(delay);

이렇게하면 ReSharper에서 "로컬 변수를 사용하지 않습니다"라는 메시지가 나타납니다.


예, 경고를 억제하지만 실제로는 아무것도 해결하지 못합니다. Task-반환 할만한 await이유가 없으면 리턴 함수를 사용해야합니다 . 여기서 작업을 폐기하는 것이 이미 사용 된 async void방법 을 사용하는 것보다 더 나은 이유는 없습니다 .

또한 void 방법을 선호합니다. 이렇게하면 StackOverflow가 Microsoft보다 더 똑똑해 지지만 당연히 제공되어야합니다.
devlord

4
async void오류 처리와 관련하여 심각한 문제가 발생하여 테스트 할 수없는 코드가 생성됩니다 ( MSDN 기사 참조 ). 이 변수를 사용하는 것이 훨씬 더 좋을 것이다 - 당신이 있다면 절대적으로 확신 당신이 자동으로 삼켜 예외를 원하는. 아마도 op는 2를 시작한 Task다음을 원할 것 await Task.WhenAll입니다.
Stephen Cleary

@StephenCleary 관찰되지 않은 작업의 예외를 무시할지 여부를 구성 할 수 있습니다. 다른 사람의 프로젝트에서 코드가 사용될 가능성이 있다면 그렇게하지 마십시오. 간단한 async void DoNotWait(Task t) { await t; }도우미 메서드를 사용하여 async void설명 하는 메서드 의 단점을 피할 수 있습니다 . (그리고 Task.WhenAllOP가 원하는 것이 아니라고 생각 하지만, 그럴 수도있다.)

@ hvd : 도우미 메소드는 테스트 가능하지만 예외 처리 지원은 여전히 ​​매우 좋지 않습니다.
Stephen Cleary

3

여기 간단한 해결책이 있습니다.

public static class TasksExtensions
{
    public static void RunAndForget(this Task task)
    {
    }
}

문안 인사


1

superflous 코드를 일으키는 간단한 예제입니다. 일반적으로 프로그램의 어느 시점에서 차단 소스에서 가져온 데이터를 사용하려고하므로 데이터를 얻을 수 있도록 결과를 다시 원할 것입니다.

실제로 프로그램의 나머지 부분과 완전히 분리 된 것이 있다면 비동기가 올바른 접근 방식이 아닙니다. 해당 작업에 대한 새 스레드를 시작하십시오.


내가 데이터가 차단 소스에서 가져온 사용하려면,하지만 난 그것을 기다리는 동안 호출자를 차단하고 싶지 않아요. 비동기가 없으면 콜백을 전달하여이 작업을 수행 할 수 있습니다. 내 예제에는 순차적으로 호출 해야하는 두 개의 비동기 메서드가 있으며 두 번째 호출은 첫 번째에서 반환 한 값을 사용해야합니다. 이것은 콜백 핸들러를 중첩시키는 것을 의미합니다. 내가 읽어 봤는데 문서는이 구체적으로 어떤이다 제안 async(청소하도록 설계되었습니다 예를 들어 )
진흙

@ Mud : 첫 번째 비동기 호출의 결과를 매개 변수로 두 번째 호출에 보냅니다. 이렇게하면 두 번째 메서드의 코드가 즉시 시작되며 느낌이들 때 첫 번째 호출의 결과를 기다릴 수 있습니다.
Guffa

나는 할 수 있지만 비동기가 없으면 중첩 콜백을 의미합니다. MethodWithCallback((result1) => { Use(result1); MethodWithCallback((result2) => { Use(result1,result2); }) 합니다. 내가 쓸 때 비동기로, 해당 코드는 나를 위해 생성 result1 = await AsyncMethod(); Use(result1); result2 = await AsyncMethod(); Use(result1,result2); 읽기 훨씬 쉽게 어느 (어느 쪽이 댓글에 함께 매우 읽을 박살되지 않습니다 불구하고!)
진흙

@ 머드 : 예. 그러나 첫 번째 직후에 두 번째 비동기 메소드를 호출 할 수 있으므로 동기 호출이에 대기 할 이유가 없습니다 Use.
Guffa

나는 당신이 말하는 것을 정말로 얻지 못합니다. MethodWithCallback은 비동기입니다. 첫 번째 전화를 기다리지 않고 다시 전화를 걸면 두 번째 전화가 첫 번째 전화보다 먼저 완료 될 수 있습니다. 그러나 두 번째 핸들러의 첫 번째 호출 결과가 필요합니다. 첫 번째 전화를 기다려야합니다.
Mud

0

정말로 결과를 무시하고 싶습니까? 예기치 않은 예외를 무시하는 것을 포함하여?

그렇지 않다면이 질문을보고 싶을 것입니다 : Fire and Forget approach ,


0

메소드 서명을 반환하도록 변경하지 않으려면 void(반환 void이 항상 void 이어야 함 ) C # 7.0 + Discard 기능을 사용할 수 있습니다. 이 기능 은 변수에 할당하는 것보다 약간 낫습니다. 소스 검증 도구 경고) :

public static void DoStuff()
{
    _ = GetNameAsync(); // we don't need the return value (suppresses warning)
    MainWorkOfApplicationIDontWantBlocked();
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.