web-dev-qa-db-ja.com

開始は、promiseスタイルのタスクでは呼び出されない場合があります。例外が来ています

シンプルなwpfデスクトップアプリケーションを作成しています。 UIには、ボタンと.csファイルのようなコードがあります。

private void Button_Click_2(object sender, RoutedEventArgs e)
{
    FunctionA();
}

public void FunctionA()
{
    Task.Delay(5000).Start();
    MessageBox.Show("Waiting Complete");
}

しかし、驚くべきことにTask.Delay(5000).Start();行はInvalidOperationExceptionをスローしています:

開始は、promiseスタイルのタスクでは呼び出されない場合があります。

なぜこのようなことができるのですか?

102
D J

Taskクラスがタスクを提供する前に既にタスクを開始しているため、このエラーが発生しています。コンストラクターを呼び出して作成するタスクでのみStartを呼び出す必要があり、作成時にタスクを開始しないという説得力のある理由がない限り、それを行うべきではありません。すぐに開始したい場合は、Task.RunまたはTask.Factory.StartNewを使用して、新しいTaskの作成と開始の両方を行う必要があります。

そのため、今では厄介なStartを取り除くだけです。コードを実行すると、5秒後ではなく、メッセージボックスがすぐに表示されることがわかります。

Task.Delayは、5秒で完了するタスクを提供します。 5秒間スレッドの実行を停止しません。あなたがしたいことは、そのタスクが完了した後に実行されるコードを持っていることです。それがContinueWithの目的です。特定のタスクが完了した後、いくつかのコードを実行できます。

public void FunctionA()
{
    Task.Delay(5000)
    .ContinueWith(t => 
    {
        MessageBox.Show("Waiting Complete");
    });
}

これは期待どおりに動作します。

C#5.0のawaitキーワードを活用して、継続をより簡単に追加することもできます。

public async Task FunctionA()
{
    await Task.Delay(5000);
    MessageBox.Show("Waiting Complete");
}

ここで何が起こっているかの完全な説明はこの質問の範囲を超えていますが、最終的な結果は以前の方法と非常によく似た方法です。メソッドを呼び出してから5秒後にメッセージボックスが表示されますが、どちらの場合もメソッド自体はすぐに(ほぼ)戻ります。そうは言っても、awaitは非常に強力であり、シンプルでわかりやすいメソッドを記述できますが、ContinueWithを直接使用して記述するのははるかに難しく、面倒です。また、エラー処理の処理を大幅に簡素化し、多くの定型コードを取り除きます。

157
Servy

これを試して。

private void Button_Click_2(object sender, RoutedEventArgs e)
{
    FunctionA();
}

public async void FunctionA()
{
    await Task.Delay(5000);
    MessageBox.Show("Waiting Complete");
}