この呼び出しを待機していないことを警告し、現在のメソッドの実行を続行します


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非同期でない場合、このメソッドはコールバック内にコールバックをネストする必要があります。asyncを使用すると、非常に自然な方法でメソッドを記述できます。

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
ところで、実装PromptForStringAsyncするときは、必要以上の作業を行います。の結果を返すだけですTask.Factory.StartNew。既にタスクであり、その値はコンソールに入力された文字列です。結果を返すまで待つ必要はありません。そうしても新しい価値はありません。
2013

それはより多くの意味を作るWwouldn'tためGetNameAsync、すなわち(ユーザーによって提供されたフルネーム提供するためにTask<Name>、というだけで返すよりもTaskDoStuffそのタスクを格納することができ、そしてどちらかawaitそれをした後、他の方法、あるいはそのほかにタスクを渡すをメソッドを実装できるようにするawaitWait、実装内のどこかに
Servy

@Servy:タスクを返すだけの場合、「これは非同期メソッドであるため、戻り式は「Task <string>」ではなく「string」型である必要があります」というエラーが表示されます。

1
asyncキーワードを削除してください。
2013

13
IMO、これはC#チームの警告に対する不適切な選択でした。警告はほぼ間違いなく間違っているもののためのものであるべきです。非同期メソッドを「起動して忘れる」場合や、実際にそれを待機したい場合が多くあります。
MgSam 2013年

回答:


103

結果が本当に必要ない場合は、単にGetNameAsyncの署名を変更して返すことができますvoid

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

関連する質問への回答を見ることを検討してください: voidを返すこととタスクを返すことの違いは何ですか?

更新

結果が必要な場合は、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
これは、今回だけ結果を必要としないのとは対照的に、結果を気にしない場合にのみ当てはまります。
2013

3
@Servy、そうですね、説明ありがとうございます。しかし、OP GetNameAsyncは値を返しません(もちろん、結果自体を除きます)。
Nikolay Khil 2013

2
正解ですが、タスクを返すことで、非同期操作の実行がいつ完了するかを知ることができます。が返された場合void、彼はそれがいつ完了したかを知る方法がありません。前回のコメントで「結果」と言ったのはそのためです。
2013

29
一般的なルールとして、async voidイベントハンドラー以外のメソッドを作成することはできません。
Daniel Mann

2
ここでもう1つ注意すべき点は、async void捕捉されない例外に切り替えると、監視されない例外に関しては動作が異なり、プロセスがクラッシュしますが、.net 4.5では実行され続けます。
Caleb Vear 2015

62

私はこの議論にかなり遅れていますが、#pragmaプリプロセッサディレクティブを使用するオプションもあります。私はあちこちに非同期コードがあり、特定の条件で明示的に待機したくないので、他の人と同じように警告と未使用の変数は嫌いです。

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

これ"4014"は、このMSDNページから来ています:コンパイラ警告(レベル1)CS4014

こちらのhttps://stackoverflow.com/a/12145047/928483の @ ryan-horathによる警告/回答もご覧ください

待機していない非同期呼び出し中にスローされた例外は失われます。この警告を取り除くには、非同期呼び出しのTask戻り値を変数に割り当てる必要があります。これにより、スローされた例外にアクセスできるようになります。例外は戻り値で示されます。

C#7.0の更新

C#7.0では、新しい機能である変数の破棄:破棄-C#ガイドが追加されました。これもこの点で役立ちます。

_ = SomeMethodAsync();

5
それは絶対に素晴らしいです。あなたがこれをできるとは思いもしませんでした。ありがとうございました!
Maxim Gershkovich 2017

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. voidを返すこととタスクを返すことの違いは何ですか?
  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; });

あなたのタスク拡張は素晴らしいです。なぜこれまで実装していないのかわかりません。
ミカエルドゥイブリンダー2013

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 2015

あなたはサーアップする必要があります!
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 2014

1
@ShitalShah
ジェイウィック

@ShitalShahは、で作成されたタスクなどの自動起動タスクでのみ機能しasync Taskます。一部のタスクは手動で開始する必要があります。
ジョナサンアレン

7

この警告に関するマイクロソフトの記事によると、返されたタスクを変数に割り当てるだけで解決できます。以下は、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は、あなたがそうしないという非常に正当な理由がない限り-ed すべきです。タスクを破棄することが、async voidメソッドを使用するというすでに受け入れられている回答よりも優れている理由はここにはありません。

私もvoidメソッドを好みます。これにより、StackOverflowはMicrosoftよりも賢くなりますが、もちろんそれは当然のことです。
devlord、2013年

4
async voidエラー処理に関する重大な問題が発生し、テスト不可能なコードが生成されます(MSDNの記事を参照)。変数を使用する方がはるかに優れています- 例外を通知せずに黙って飲みたいと確信している場合。より可能性が高いのは、opが2つTaskのを開始してからを実行することawait Task.WhenAllです。
Stephen Cleary 2013年

@StephenCleary監視されていないタスクからの例外が無視されるかどうかは構成可能です。コードが他の誰かのプロジェクトで使用される可能性がある場合は、それを起こさないでください。単純なasync void DoNotWait(Task t) { await t; }ヘルパーメソッドを使用しasync voidて、記述したメソッドの欠点を回避できます。(そして私はTask.WhenAllOPが望んでいることだとは思いませんが、それは非常によくあり得ます。)

@hvd:ヘルパーメソッドはテスト可能にしますが、例外処理のサポートは非​​常に貧弱です。
スティーブンクリアリー2013年

3

ここでは、簡単なソリューションです。

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

よろしく


1

不要なコードを作成するのは、あなたの単純化した例です。通常は、プログラムのある時点でブロッキングソースからフェッチされたデータを使用するので、データにアクセスできるように結果を戻す必要があります。

プログラムの残りの部分から完全に切り離されて発生することが本当にある場合、非同期は適切なアプローチではありません。そのタスクの新しいスレッドを開始するだけです。


私がやるのデータは、ブロッキングソースからフェッチ使用したいが、私はそれを待っている間、発信者をブロックする必要はありません。非同期がない場合、コールバックを渡すことでこれを実現できます。私の例では、2つの非同期メソッドを順番に呼び出す必要があり、2番目の呼び出しは最初の呼び出しによって返された値を使用する必要があります。これは、地獄のように醜くなる、コールバックハンドラをネストすることを意味します。私が読んでいるドキュメントは、これが特にasyncクリーンアップするために設計されたものであることを示唆していますたとえば
Mud

@Mud:最初の非同期呼び出しの結果をパラメーターとして2番目の呼び出しに送信するだけです。このようにして、2番目のメソッドのコードはすぐに開始され、最初の呼び出しの結果が気になるときに待つことができます。
Guffa

私はそれを行うことができますが、非同期を使用しないと、次のようにコールバックがネストされますMethodWithCallback((result1) => { Use(result1); MethodWithCallback((result2) => { Use(result1,result2); })。asyncを使用すると、書き込み時に同等のコードが生成されます。result1 = await AsyncMethod(); Use(result1); result2 = await AsyncMethod(); Use(result1,result2); どちらのコードが非常に読みやすくなります(ただし、このコメントでは、どちらも非常に読みやすくまとめられています)
Mud

@マッド:はい。ただし、最初の非同期メソッドの直後に2番目の非同期メソッドを呼び出すことができます。このメソッドがへの同期呼び出しを待つ理由はありませんUse
Guffa

あなたの言っていることが本当にわかりません。MethodWithCallbackは非同期です。最初の通話を待たずに電話をかけ直した場合、2番目の通話が最初の通話より先に終了することがあります。ただし、2番目のハンドラーの最初の呼び出しの結果が必要です。だから私は最初の呼び出しを待たなければなりません。
2013


0

メソッドシグネチャをreturnに変更したくない場合void(return voidは常にvoid edである必要があるため)、このようなC#7.0+の破棄機能を使用できます。ソース検証ツールの警告):

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.