「awaitTask.Run();」の違い 戻る;」および「returnTask.Run()」?


90

次の2つのコードの間に概念的な違いはありますか?

async Task TestAsync() 
{
    await Task.Run(() => DoSomeWork());
}

そして

Task TestAsync() 
{
    return Task.Run(() => DoSomeWork());
}

生成されたコードも異なりますか?

編集:との混同を避けるためTask.Runに、同様のケース:

async Task TestAsync() 
{
    await Task.Delay(1000);
}

そして

Task TestAsync() 
{
    return Task.Delay(1000);
}

後期更新:受け入れられた回答に加えて、LocalCallContext処理方法にも違いがあります。非同期がない場合でも、CallContext.LogicalGetDataが復元されます。どうして?


1
はい、違います。そしてそれは大きく異なります。そうでなければ、await/asyncを使用しても意味がありません:)
MarcinJuraszek 2014年

1
ここには2つの質問があると思います。1.メソッドの実際の実装は、呼び出し元にとって重要ですか?2. 2つのメソッドのコンパイルされた表現は異なりますか?
DavidRR 2016年

回答:


80

大きな違いの1つは、例外の伝播です。 内部スローされた例外、async Taskこの方法は、返さに格納されている取得しTaskたオブジェクトとタスクを介して観察されるまで休止状態のままでawait tasktask.Wait()task.Resultまたはtask.GetAwaiter().GetResult()。メソッドの同期部分からスローされた場合でも、この方法で伝播されasyncます。

次のコードについて考えてみます。ここでOneTestAsync、とのAnotherTestAsync動作はまったく異なります。

static async Task OneTestAsync(int n)
{
    await Task.Delay(n);
}

static Task AnotherTestAsync(int n)
{
    return Task.Delay(n);
}

// call DoTestAsync with either OneTestAsync or AnotherTestAsync as whatTest
static void DoTestAsync(Func<int, Task> whatTest, int n)
{
    Task task = null;
    try
    {
        // start the task
        task = whatTest(n);

        // do some other stuff, 
        // while the task is pending
        Console.Write("Press enter to continue");
        Console.ReadLine();
        task.Wait();
    }
    catch (Exception ex)
    {
        Console.Write("Error: " + ex.Message);
    }
}

を呼び出すとDoTestAsync(OneTestAsync, -2)、次の出力が生成されます。

Enterキーを押して続行します
エラー:1つ以上のエラーが発生しました。awaitTask.Delay
エラー:2番目

注意してください、私はEnterそれを見るために押す必要がありました。

さて、を呼び出すとDoTestAsync(AnotherTestAsync, -2)、内部のコードワークフローDoTestAsyncはまったく異なり、出力も異なります。今回、私は押すように頼まれませんでしたEnter

エラー:値は-1(無限のタイムアウトを意味する)、0、または正の整数のいずれかである必要があります。
パラメータ名:milliaseDelayError:1番目

どちらの場合もTask.Delay(-2)、パラメータを検証しながら、最初にスローします。これは作り上げのシナリオかもしれませんが、理論的にTask.Delay(1000)は、たとえば、基盤となるシステムタイマーAPIが失敗した場合にもスローされる可能性があります。

ちなみに、エラー伝播ロジックは(async voidメソッドとは対照async Task的に)メソッドではまだ異なります。async voidメソッド内で発生した例外は、現在のスレッドに同期コンテキストがあるSynchronizationContext.Post場合は(を介して)、現在のスレッドの同期コンテキストですぐに再スローされます(SynchronizationContext.Current != null)。それ以外の場合は、を介して再スローされますThreadPool.QueueUserWorkItem)。呼び出し元には、同じスタックフレームでこの例外を処理する機会がありません。

TPL例外処理の動作に関する詳細をここここに投稿しました


Qasync非同期Taskベースではないメソッドのメソッドの例外伝播動作を模倣して、後者が同じスタックフレームにスローされないようにすることは可能ですか?

A:本当に必要な場合は、はい、そのためのトリックがあります:

// async
async Task<int> MethodAsync(int arg)
{
    if (arg < 0)
        throw new ArgumentException("arg");
    // ...
    return 42 + arg;
}

// non-async
Task<int> MethodAsync(int arg)
{
    var task = new Task<int>(() => 
    {
        if (arg < 0)
            throw new ArgumentException("arg");
        // ...
        return 42 + arg;
    });

    task.RunSynchronously(TaskScheduler.Default);
    return task;
}

ただし、特定の条件下(スタックの深すぎる場合など)RunSynchronouslyでも、非同期で実行される可能性があることに注意してください。


もう1つの注目すべき違いはasync/awaitバージョンはデフォルト以外の同期コンテキストでデッドロックする傾向があることです。たとえば、WinFormsまたはWPFアプリケーションでは次のものがデッドロックします。

static async Task TestAsync()
{
    await Task.Delay(1000);
}

void Form_Load(object sender, EventArgs e)
{
    TestAsync().Wait(); // dead-lock here
}

非同期バージョンに変更すると、デッドロックは発生しません。

Task TestAsync() 
{
    return Task.Delay(1000);
}

デッドロックの性質は、StephenClearyのブログで詳しく説明されてます。


2
最初の例のデッドロックは、メソッドが同じ実行コンテキストに戻ろうとしているためにのみ発生するため、await行に.ConfigureAwait(false)を追加することで回避できると思います。したがって、残っている違いは例外だけです。
比較的

2
答えは違いについてだったものの@relatively_randomは、あなたのコメントは、正確であるreturn Task.Run()await Task.Run(); return、いうよりawait Task.Run().ConfigureAwait(false); return
noseratio

Enterキーを押した後にプログラムが終了する場合は、F5ではなくctrl + F5を実行してください。
DavidKlempfner19年

54

違いは何ですか

async Task TestAsync() 
{
    await Task.Delay(1000);
}

そして

Task TestAsync() 
{
    return Task.Delay(1000);
}

私はこの質問に混乱しています。あなたの質問に別の質問で答えることによって明確にしようと思います。違いは何ですか?

Func<int> MakeFunction()
{
    Func<int> f = ()=>1;
    return ()=>f();
}

そして

Func<int> MakeFunction()
{
    return ()=>1;
}

私の2つのものの違いが何であれ、同じ違いがあなたの2つのものの間にあります。


22
もちろん!あなたは私の目を開いた:)最初のケースでは、意味的にに近いラッパータスクを作成しますTask.Delay(1000).ContinueWith(() = {})。2番目のものでは、それはただTask.Delay(1000)です。違いはやや微妙ですが、重要です。
avo 2014年

3
違いを少し説明していただけますか?実際には私はしません..ありがとう
zhengyu18年

4
同期コンテキストと例外の伝播には微妙な違いがあることを考えると、async / awaitラッパーと関数ラッパーの違いは同じではないと思います。
キャメロンマクファーランド2018

1
@CameronMacFarland:それが私が説明を求めた理由です。質問は、2つの間に概念的な違いがあるかどうかを尋ねます。まあ、わかりません。確かに多くの違いがあります。それらのいずれかが「概念的な」違いとしてカウントされますか?ネストされた関数を使用した私の例では、エラーの伝播にも違いがあります。関数がローカル状態で閉じられている場合、ローカルの有効期間に違いがあります。これらの「概念的な」違いはありますか?
エリックリペット2018

6
これは古い答えですが、今日与えられたとしたら、反対票を投じられたと思います。それは質問に答えたり、OPを彼が学ぶことができる情報源に向けたりするものではありません。
ダニエルドゥボフスキー

11
  1. 最初のメソッドはコンパイルすらしません。

    ' Program.TestAsync()'は' 'を返す非同期メソッドであるTaskため、returnキーワードの後に​​オブジェクト式を続けることはできません。戻るつもりでしたTask<T>か?

    それはする必要があります

    async Task TestAsync()
    {
        await Task.Run(() => DoSomeWork());
    }
    
  2. これら2つの間に大きな概念上の違いがあります。最初のものは非同期ですが、2番目のものは非同期ではありません。非同期パフォーマンスを読む:非同期のコストを理解し、async /の内部についてもう少し理解するのを待ちawaitます。

  3. それらは異なるコードを生成します。

    .method private hidebysig 
        instance class [mscorlib]System.Threading.Tasks.Task TestAsync () cil managed 
    {
        .custom instance void [mscorlib]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [mscorlib]System.Type) = (
            01 00 25 53 4f 54 65 73 74 50 72 6f 6a 65 63 74
            2e 50 72 6f 67 72 61 6d 2b 3c 54 65 73 74 41 73
            79 6e 63 3e 64 5f 5f 31 00 00
        )
        .custom instance void [mscorlib]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() = (
            01 00 00 00
        )
        // Method begins at RVA 0x216c
        // Code size 62 (0x3e)
        .maxstack 2
        .locals init (
            [0] valuetype SOTestProject.Program/'<TestAsync>d__1',
            [1] class [mscorlib]System.Threading.Tasks.Task,
            [2] valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder
        )
    
        IL_0000: ldloca.s 0
        IL_0002: ldarg.0
        IL_0003: stfld class SOTestProject.Program SOTestProject.Program/'<TestAsync>d__1'::'<>4__this'
        IL_0008: ldloca.s 0
        IL_000a: call valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::Create()
        IL_000f: stfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder SOTestProject.Program/'<TestAsync>d__1'::'<>t__builder'
        IL_0014: ldloca.s 0
        IL_0016: ldc.i4.m1
        IL_0017: stfld int32 SOTestProject.Program/'<TestAsync>d__1'::'<>1__state'
        IL_001c: ldloca.s 0
        IL_001e: ldfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder SOTestProject.Program/'<TestAsync>d__1'::'<>t__builder'
        IL_0023: stloc.2
        IL_0024: ldloca.s 2
        IL_0026: ldloca.s 0
        IL_0028: call instance void [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::Start<valuetype SOTestProject.Program/'<TestAsync>d__1'>(!!0&)
        IL_002d: ldloca.s 0
        IL_002f: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder SOTestProject.Program/'<TestAsync>d__1'::'<>t__builder'
        IL_0034: call instance class [mscorlib]System.Threading.Tasks.Task [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::get_Task()
        IL_0039: stloc.1
        IL_003a: br.s IL_003c
    
        IL_003c: ldloc.1
        IL_003d: ret
    } // end of method Program::TestAsync
    

    そして

    .method private hidebysig 
        instance class [mscorlib]System.Threading.Tasks.Task TestAsync2 () cil managed 
    {
        // Method begins at RVA 0x21d8
        // Code size 23 (0x17)
        .maxstack 2
        .locals init (
            [0] class [mscorlib]System.Threading.Tasks.Task CS$1$0000
        )
    
        IL_0000: nop
        IL_0001: ldarg.0
        IL_0002: ldftn instance class [mscorlib]System.Threading.Tasks.Task SOTestProject.Program::'<TestAsync2>b__4'()
        IL_0008: newobj instance void class [mscorlib]System.Func`1<class [mscorlib]System.Threading.Tasks.Task>::.ctor(object, native int)
        IL_000d: call class [mscorlib]System.Threading.Tasks.Task [mscorlib]System.Threading.Tasks.Task::Run(class [mscorlib]System.Func`1<class [mscorlib]System.Threading.Tasks.Task>)
        IL_0012: stloc.0
        IL_0013: br.s IL_0015
    
        IL_0015: ldloc.0
        IL_0016: ret
    } // end of method Program::TestAsync2
    

@MarcinJuraszek、確かにそれはコンパイルされませんでした。それはタイプミスでした、私はあなたがそれを正しく理解したと確信しています。そうでなければ、素晴らしい答え、ありがとう!最初のケースでは、C#はステートマシンクラスの生成を回避するのに十分賢いのではないかと思いました。
avo 2014年

9

2つの例異なります。メソッドがasyncキーワードでマークされると、コンパイラーはバックグラウンドでステートマシンを生成します。これは、待機可能なものが待機された後、継続を再開する責任があります。

対照的に、メソッドがマークされていない場合、待機asyncする機能が失われますawait。(つまり、メソッド自体の中で、メソッドは呼び出し元が待機することができます。)ただし、asyncキーワードを回避することにより、ステートマシンを生成しなくなり、かなりのオーバーヘッドが追加される可能性があります(ローカルをフィールドに持ち上げる)ステートマシンの、GCへの追加オブジェクト)。

このような例ではasync-await、待機を回避して直接返すことができる場合は、メソッドの効率を向上させるために実行する必要があります。

あなたの質問とこの回答に非常によく似ているこの質問この回答を参照してください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.