別のタスクを待つ開始されていないタスクを宣言する方法は?


9

私はこの単体テストを実行しましたが、「await Task.Delay()」が待機しない理由がわかりません!

   [TestMethod]
    public async Task SimpleTest()
    {
        bool isOK = false;
        Task myTask = new Task(async () =>
        {
            Console.WriteLine("Task.BeforeDelay");
            await Task.Delay(1000);
            Console.WriteLine("Task.AfterDelay");
            isOK = true;
            Console.WriteLine("Task.Ended");
        });
        Console.WriteLine("Main.BeforeStart");
        myTask.Start();
        Console.WriteLine("Main.AfterStart");
        await myTask;
        Console.WriteLine("Main.AfterAwait");
        Assert.IsTrue(isOK, "OK");
    }

ユニットテストの出力は次のとおりです。

ユニットテスト出力

これはどのようにして「待機」が待機せず、メインスレッドが継続するのでしょうか。


あなたが何を達成しようとしているのかは少し不明確です。期待される出力を追加できますか?
OlegI

1
テスト方法は非常に明確です-isOKは真実であると期待されています
サー

開始されていないタスクを作成する理由はありません。タスクはスレッドではなく、スレッドを使用します。あなたは何をしようとしているのですか?Task.Run()最初の後で使ってみませんConsole.WriteLineか?
Panagiotis Kanavos

1
@Eloスレッドプールについて説明しました。あなたはしていないジョブキューを実装するためのタスクのプールを必要とします。ジョブのキューが必要です。例:Action <T>オブジェクト
Panagiotis Kanavos

2
@Eloあなたがやろうとしていることは既に.NETで利用可能です。たとえば、ActionBlockのようなTPL Dataflowクラス、または新しいSystem.Threading.Channelsクラスを介して。ActionBlockを作成して、1つ以上の同時タスクを使用してメッセージを受信および処理できます。すべてのブロックには、構成可能な容量を持つ入力バッファーがあります。DOPと容量により、同時実行性を制御し、リクエストを抑制し、
バックプレッシャー

回答:


8

new Task(async () =>

タスクはかかりませんFunc<Task>が、Action。非同期メソッドを呼び出し、戻り時に終了することを期待します。しかし、そうではありません。タスクを返します。そのタスクは新しいタスクによって待機されていません。新しいタスクの場合、メソッドが返されるとジョブが実行されます。

新しいタスクでラップする代わりに、既存のタスクを使用する必要があります。

[TestMethod]
public async Task SimpleTest()
{
    bool isOK = false;

    Func<Task> asyncMethod = async () =>
    {
        Console.WriteLine("Task.BeforeDelay");
        await Task.Delay(1000);
        Console.WriteLine("Task.AfterDelay");
        isOK = true;
        Console.WriteLine("Task.Ended");
    };

    Console.WriteLine("Main.BeforeStart");
    Task myTask = asyncMethod();

    Console.WriteLine("Main.AfterStart");

    await myTask;
    Console.WriteLine("Main.AfterAwait");
    Assert.IsTrue(isOK, "OK");
}

4
Task.Run(async() => ... )オプションでもあります
ルフォ卿

あなたはちょうど質問の著者と同じことをしました
OlegI '15

ところで、WW myTask.Start();がレイズしますInvalidOperationException
Sir Rufo

@OlegI見えません。説明していただけますか?
nvoigt

@nvoigt私はmyTask.Start()彼が彼の選択肢の例外を生み出すことを意味し、を使用するときに削除する必要があることを想定していTask.Run(...)ます。ソリューションにエラーはありません。
404

3

問題は、非ジェネリックTaskクラスを使用していることです。これは、結果を生成するためのものではありません。したがってTask、非同期デリゲートを渡してインスタンスを作成すると、次のようになります。

Task myTask = new Task(async () =>

...デリゲートはとして扱われasync voidます。Anはasync voidないがTask、その例外が処理することができない、待たすることができず、それが元だ質問の何千人もここにStackOverflowで、他の場所挫折プログラマによって作られました。解決策は、ジェネリックTask<TResult>クラスを使用することですTask。これは、結果を返す必要があり、その結果が別のであるためです。だからあなたは作成する必要がありますTask<Task>

Task<Task> myTask = new Task<Task>(async () =>

これでStart、アウターTask<Task>を作成すると、インナーを作成するだけなので、ほとんど瞬時に完了しますTask。その後Task、同様にインナーを待つ必要があります。これは、それを行う方法です。

myTask.Start();
Task myInnerTask = await myTask;
await myInnerTask;

2つの選択肢があります。内部への明示的な参照が必要ない場合はTask、外部をTask<Task>2回待つだけです。

await await myTask;

...またはUnwrap、外部タスクと内部タスクを1つに結合する組み込みの拡張メソッドを使用できます。

await myTask.Unwrap();

このアンラップTask.Runは、ホットタスクを作成するより一般的な方法を使用すると自動的にUnwrap行われるため、現在ではあまり使用されていません。

非同期デリゲートが結果(たとえば)を返す必要があると判断した場合はstringmyTask変数の型を宣言する必要がありますTask<Task<string>>

注:Taskコールドタスクを作成するためのコンストラクターの使用を推奨しません。慣例は一般的には見当がつかないので、理由は本当にわかりませんが、おそらくあまり使用されていないため、他の知らないユーザー/メンテナー/コードのレビュー担当者を驚かせる可能性があるためです。

一般的なアドバイス:メソッドの引数として非同期デリゲートを提供するときは常に注意してください。このメソッドは、理想的にはFunc<Task>引数(非同期デリゲートを理解することを意味する)、または少なくともFunc<T>引数(少なくとも生成Taskされたものが無視されないことを意味する)を期待する必要があります。このメソッドがを受け入れるという残念なケースでは、Actionデリゲートはとして扱われasync voidます。これがあなたが望むものであることはめったにありません。


どのように詳細な技術的回答!ありがとうございました。
Elo

@エロ私の喜び!
Theodor Zoulias

1
 [Fact]
        public async Task SimpleTest()
        {
            bool isOK = false;
            Task myTask = new Task(() =>
            {
                Console.WriteLine("Task.BeforeDelay");
                Task.Delay(3000).Wait();
                Console.WriteLine("Task.AfterDelay");
                isOK = true;
                Console.WriteLine("Task.Ended");
            });
            Console.WriteLine("Main.BeforeStart");
            myTask.Start();
            Console.WriteLine("Main.AfterStart");
            await myTask;
            Console.WriteLine("Main.AfterAwait");
            Assert.True(isOK, "OK");
        }

ここに画像の説明を入力してください


3
がなければawait、タスクの遅延は実際にはそのタスクを遅延させません。機能を削除しました。
nvoigt

テストしたところ、@ nvoigtは正しい:経過時間:0:00:00,0106554。スクリーンショットに「経過時間:18ミリ秒」と表示されます。1000ミリ秒以上である必要があります
Elo

はい、あなたは正しい、私は私の応答を更新します。Tnx。あなたがコメントした後、最小限の変更でこれを解決します。:)
BASKA

1
タスク内から待機せずにwait()を使用することをお勧めします。ありがとう!
Elo、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.