「警告CS4014:この呼び出しが待機されていないため、現在のメソッドの実行が継続しています...」の抑制


156

これは、「待機せずにC#で非同期メソッドを安全に呼び出す方法」の複製ではありません。

次の警告を適切に抑制するにはどうすればよいですか?

警告CS4014:この呼び出しは待機されていないため、現在のメソッドの実行は、呼び出しが完了する前に続行されます。呼び出しの結果に「待機」演算子を適用することを検討してください。

簡単な例:

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
    // ...
}

更新元の承認された回答が編集されたためContinueWithここでは適切ではないと思うので、承認された回答をC#7.0破棄を使用するものに変更しました。ファイアアンドフォーゲット操作の例外をログに記録する必要があるときはいつでも、Stephen Cleary提案したより精巧なアプローチを使用します


1
それで、あなた#pragmaは良くないと思いますか?
フレデリックハミディ2014年

10
@FrédéricHamidi、そうです。
noseratio 14年

2
@Noseratio:ああ、そうです。すみません、もう一つの警告だと思いました。私を無視!
Jon Skeet、2014年

3
@Terribad:よくわかりません-ほとんどの場合、警告はかなり合理的です。特に、あなたはどんな障害に起こるしたいかを考える必要があります-通常さえのための「火と忘れて」あなたはなどの障害ログに記録する方法を動作するはずです
ジョンスキート

4
@Terribad、この方法を使用する前に、非同期メソッドの例外がどのように伝播されるかを明確に把握する必要があります(これを確認してください)。次に、@Knaģisの答えは、ヘルパーメソッドを使用して、fire-and-forgetの例外を失うことのないエレガントな方法を提供しますasync void
noseratio 14

回答:


160

C#7では、破棄を使用できるようになりました。

_ = WorkAsync();

7
これは私が覚えていないだけの便利な言語機能です。それ_ = ...は私の脳にあるようなものです。
マーク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 14年

1
@NoseratioとKnagisは、このアプローチが気に入っており、使用する予定です。関連するフォローアップの質問を投稿しました:stackoverflow.com/questions/22864367/fire-and-forget-approach
Matt Smith

3
@stricq Forget()にConfigureAwait(false)を追加する目的は何ですか?私が理解しているように、ConfigureAwaitは、タスクでawaitが使用された時点でのみスレッドの同期に影響を与えますが、Forget()の目的はタスクを破棄することなので、タスクを決して待つことができないため、ここでのConfigureAwaitは無意味です。
dthorpe 2016

3
起動して忘れるタスクが完了する前にスポーンスレッドが消えても、ConfigureAwait(false)がないと、スポーンスレッドは自身をマーシャリングしてスポーンスレッドに戻そうとしますが、そのスレッドはなくなってデッドロックになります。ConfigureAwait(false)を設定すると、呼び出しスレッドにマーシャリングしないようにシステムに指示します。
stricq 2016

2
この返信には、特定のケースを管理するための編集と、数十のコメントがあります。単に物事はしばしば適切なものです、廃棄のために行きます!そして、@ fjch1997の回答を引用します。警告を抑制する目的で、実行するのにさらに数ティックを要するメソッドを作成するのは愚かです。
Teejay

39

次の属性を使用してメソッドを装飾できます。

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

基本的に、コンパイラーに自分が何をしているのかを知っていて、起こり得る間違いを心配する必要がないことを伝えています。

このコードの重要な部分は2番目のパラメーターです。「CS4014:」の部分は警告を抑制します。残りは何でも書けます。


動作しません:Visual Studio for Mac 7.0.1(ビルド24)。それはそうであるように見えます-いや。
IronRod 2017

1
[SuppressMessage("Compiler", "CS4014")]エラー一覧ウィンドウのメッセージを抑制しますが、出力ウィンドウには引き続き警告行が表示されます
David Ching

35

これに対処する2つの方法。

それを破棄変数に保存します(C#7)

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

C#7で破棄が導入されて以来、これは警告を抑制するよりも優れていると考えています。それは警告を抑制するだけでなく、火災のことを忘れる意図を明確にするからです。

さらに、コンパイラーはリリースモードで最適化することができます。

それを抑えるだけ

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

「ファイアアンドフォーゲット」を解決するのに十分なソリューションです。

この警告が表示されるのは、多くの場合、待機せずにタスクを返すメソッドを使用する意図がないためです。発砲して忘れるつもりのときに警告を抑制することは理にかなっています。

スペルの方法を思い出せない場合は#pragma warning disable 4014、Visual Studioに追加してもらうだけです。Ctrl +を押します。「クイックアクション」を開き、「CS2014を非表示にする」

概して

警告を抑制するためだけに、実行にさらに数ティックを必要とするメソッドを作成するのは愚かです。


これは、Visual Studio for Mac 7.0.1(ビルド24)で機能しました。
IronRod 2017

1
それは単なる警告を抑制するために、さらにいくつかの実行に要するティックメソッドを作成するために愚かだ - IMOのすべての余分な目盛りを追加していませんこの1は、より読みやすいです:[MethodImpl(MethodImplOptions.AggressiveInlining)] void Forget(this Task @this) { } /* ... */ obj.WorkAsync().Forget();
noseratio

1
回の@Noseratio Aロット、私が使用している場合AggressiveInlining、コンパイラをただ何らかの理由でそれを無視する
fjch1997

1
プラグマオプションは非常にシンプルで、メソッド全体ではなく、コードの現在の行(またはセクション)にのみ適用されるので、私は気に入っています。
wasatchwizard 2017年

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); 
}

このアプローチについては、特に気に入らなかった問題の1つとして、質問自体で触れました。
noseratio 2017年

おっと!プラグマと同じコードセクションにあるため、気付かなかった...そして答えを探していた。それとは別に、この方法についてあなたが嫌い​​なことは何ですか?
noelicus

1
task忘れられたローカル変数のようには見えません。ほとんどコンパイラが私に別の警告を与えるのと同じように、「task割り当てられているが、その値は決して使用されていません」のようなものに加えて、そうではありません。また、コードが読みにくくなります。私自身もこのアプローチを使用ています。
noseratio 2017年

十分に公正-私はそれに名前を付けた理由は同じような感じでしたfireAndForget...なので、今後は参照されないことを期待しています。
noelicus 2017

4

警告の理由は、WorkAsync Taskが決して読み取られない、または待機されないを返すことです。WorkAsyncの戻り値の型をに設定できvoid、警告はなくなります。

通常、メソッドはTask、呼び出し元がワーカーのステータスを知る必要があるときにを返します。ファイアアンドフォーゲットの場合、呼び出し側が呼び出されたメソッドから独立しているように、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 2018
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.