ブログ@kaorun55

HoloLensやKinectなどのDepthセンサーを中心に書いています。

async/awaitがよくわからんです

つぎのC#はasync/awaitってことでいろいろ見てたんですが、挙動がいまいちつかめなかったので残しときます。

いろいろ教えてもらって、とりあえずは理解しました

最初

とりあえず、同期で待たされるメソッドを作りました。
ハズが待たされませんw

private void button1_Click(object sender, RoutedEventArgs e)
{
    this.label1.Text = "";
    var stopwatch = Stopwatch.StartNew();
    var stopwatch2 = Stopwatch.StartNew();
    this.button1.IsEnabled = false; //prevent re-entry 
    var someTask = Task<int>.Factory.StartNew(() => slowFunc(1, 2));
    someTask.ContinueWith(x =>
    {
        // 5秒後
        this.label1.Text = "Result: " + someTask.Result.ToString();
        this.button1.IsEnabled = true;
        stopwatch2.Stop();

        this.label3.Text = string.Format("{0}ms, {1}ms", stopwatch.ElapsedMilliseconds, stopwatch2.ElapsedMilliseconds);
    },

    TaskScheduler.FromCurrentSynchronizationContext());
    stopwatch.Stop();

    // 5秒後
    this.label2.Text = string.Format("{0}ms, {1}ms", stopwatch.ElapsedMilliseconds, stopwatch2.ElapsedMilliseconds);
}

// 5秒待つ
private int slowFunc(int a, int b)
{
    // ここでwaitされるハズ?
    Task.Delay(TimeSpan.FromSeconds(5));
    return a + b;
}

次に

非同期で実行されるように改造しました。
意図したとおりに実行されました

private void button1_Click(object sender, RoutedEventArgs e)
{
    this.label1.Text = "";
    var stopwatch = Stopwatch.StartNew();
    var stopwatch2 = Stopwatch.StartNew();
    this.button1.IsEnabled = false; //prevent re-entry 
    var someTask = Task<Task<int>>.Factory.StartNew(() => slowFunc(1, 2));
    someTask.ContinueWith(x =>
    {
        // slowFuncが終わってからなので、5秒後に実行される
        this.label1.Text = "Result: " + someTask.Result.Result.ToString();
        this.button1.IsEnabled = true;
        stopwatch2.Stop();

        this.label3.Text = string.Format("{0}ms, {1}ms", stopwatch.ElapsedMilliseconds, stopwatch2.ElapsedMilliseconds);
    },

    // 非同期処理なので、すぐに処理される
    TaskScheduler.FromCurrentSynchronizationContext());
    stopwatch.Stop();

    this.label2.Text = string.Format("{0}ms, {1}ms", stopwatch.ElapsedMilliseconds, stopwatch2.ElapsedMilliseconds);
}


private async Task<int> slowFunc(int a, int b)
{
    // 5秒待つ(非同期)
    await Task.Delay(TimeSpan.FromSeconds(5));
    return a + b;
}

最後に

非同期処理のawaitを外して、同期にしたつもりです。
なってなさそうです。

private void button1_Click(object sender, RoutedEventArgs e)
{
    this.label1.Text = "";
    var stopwatch = Stopwatch.StartNew();
    var stopwatch2 = Stopwatch.StartNew();
    this.button1.IsEnabled = false; //prevent re-entry 
    var someTask = Task<Task<int>>.Factory.StartNew(() => slowFunc(1, 2));
    someTask.ContinueWith(x =>
    {
        // slowFuncが終わってからなので、5秒後に実行されるハズ
        this.label1.Text = "Result: " + someTask.Result.Result.ToString();
        this.button1.IsEnabled = true;
        stopwatch2.Stop();

        this.label3.Text = string.Format("{0}ms, {1}ms", stopwatch.ElapsedMilliseconds, stopwatch2.ElapsedMilliseconds);
    },

    // 同期してるはずなので、5秒後に実行されるハズ
    TaskScheduler.FromCurrentSynchronizationContext());
    stopwatch.Stop();

    this.label2.Text = string.Format("{0}ms, {1}ms", stopwatch.ElapsedMilliseconds, stopwatch2.ElapsedMilliseconds);
}


private async Task<int> slowFunc(int a, int b)
{
    // 5秒待つ(同期)
    Task.Delay(TimeSpan.FromSeconds(5));
    return a + b;
}

ということで

awaitableのメソッドは、awaitしなさいってことなのかもしれませんが、同期実行されますよといわれるだけで、エラーにもならないのでよくわかりません。
asyncつけてるためとか?

いろいろ教えてもらいました

  • awaitをつけると、「以降の処理(a + b)」が非同期で実行される(呼び出し元に返る)
  • Task.Delay自体も非同期で実行され、awaitをつけるとDelayした後に、「以降の処理」が実行される
  • Task.Delayにawaitをつけない場合は、Delayが非同期に実行されるので、「以降の処理」はDelayと並列で実行される(ので、Delayされない)
  • awaitをつけないTask.Delayは、戻り値のTask.GetAwaiter().OnCompletedにDelay後のコールバック先を登録してTask.Wait()すると同じようになる
  • awaitをつけるTask.Delayは、Task.GetAwaiter().OnCompletedとTask.Wait()をやってるのと同じ扱い(もし、Task.GetAwaiter().IsCompletedだったら、そのまま続ける)

private void button1_Click(object sender, RoutedEventArgs e)
{
    this.label1.Text = "";
    var stopwatch = Stopwatch.StartNew();
    var stopwatch2 = Stopwatch.StartNew();
    this.button1.IsEnabled = false; //prevent re-entry 
    var someTask = Task<Task<int>>.Factory.StartNew(() => slowFunc(1, 2));
    someTask.ContinueWith(x =>
    {
        this.label1.Text = "Result: " + someTask.Result.Result.ToString();
        this.button1.IsEnabled = true;
        stopwatch2.Stop();

        this.label3.Text = string.Format("{0}ms, {1}ms", stopwatch.ElapsedMilliseconds, stopwatch2.ElapsedMilliseconds);
    },

    TaskScheduler.FromCurrentSynchronizationContext());
    stopwatch.Stop();

    this.label2.Text = string.Format("{0}ms, {1}ms", stopwatch.ElapsedMilliseconds, stopwatch2.ElapsedMilliseconds);
}


private async Task<int> slowFunc(int a, int b)
{
    var t =  Task.Delay(TimeSpan.FromSeconds(5));
    int c = 0;
    t.GetAwaiter().OnCompleted(new Action(() =>
    {
            c = a + b;
    }));

    t.Wait();

    return c;
}