1. ホーム
  2. c#

[解決済み] await Task.Run(); return;" と "return Task.Run()" の違いは何ですか?

2022-12-15 10:43:38

質問

以下の2つのコードの間に、何かコンセプトの違いがありますか?

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

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

生成されたコードにも違いがあるのでしょうか?

EDITです。 との混同を避けるため Task.Run との混同を避けるため、同様のケースを

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

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

遅れての更新です。 受理された答えに加えて、どのように LocalCallContext が処理されます。 CallContext.LogicalGetData は、非同期がない場合でも復元されます。なぜですか?

どのように解決するのですか?

大きな違いの1つは 例外の伝搬にあります。 例外は async Task メソッド内で投げられた例外は、返された Task オブジェクトに格納され、そのタスクが await task , task.Wait() , task.Result または task.GetAwaiter().GetResult() . から投げられた場合でも、この方法で伝搬されます。 同期 の部分から投げられたとしても、このように伝播します。 async メソッドを使用します。

次のコードを考えてみましょう。 OneTestAsyncAnotherTestAsync は全く異なる挙動をします。

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つ以上のエラーが発生しました。
エラー: 2回目

注、私は Enter を押さないと表示されません。

さて、もし私が DoTestAsync(AnotherTestAsync, -2) を呼び出すと、その中のコード・ワークフローは DoTestAsync の中のコードのワークフローはかなり異なっており、出力も異なっています。今回、私は Enter :

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

どちらの場合も Task.Delay(-2) はそのパラメータを検証している間、冒頭で投げます。これはでっち上げのシナリオかもしれませんが、理論的には Task.Delay(1000) も投げるかもしれません。例えば、基礎となるシステムタイマー API が失敗した場合です。

余談ですが、エラー伝搬のロジックはまだ async void メソッド (対して async Task メソッドに対して)。の中で発生した例外は async void メソッド内で発生した例外は、現在のスレッドの同期コンテキストで ( メソッドによって) すぐに再スローされます。 SynchronizationContext.Post を通して)、現在のスレッドが持っている場合( SynchronizationContext.Current != null) . そうでなければ、それは ThreadPool.QueueUserWorkItem ). 呼び出し元は同じスタックフレーム上でこの例外を処理する機会はありません。

TPLの例外処理の動作について、もう少し詳細を投稿しました。 はこちら はこちら .


Q : の例外伝播の挙動を模倣することは可能でしょうか? async メソッドを非同期 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 はまだ非同期で実行される可能性があります。


もう一つの顕著な違いは 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);
}

デッドロックの性質については、Stephen Cleary が彼の ブログ .