Bagaimana cara mendeklarasikan Tugas yang belum dimulai yang akan Menunggu untuk Tugas lain?

9

Saya telah melakukan Tes Unit ini dan saya tidak mengerti mengapa "menunggu Task.Delay ()" tidak menunggu!

   [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");
    }

Berikut ini adalah hasil Uji Unit:

Output Uji Unit

Bagaimana mungkin "menunggu" tidak menunggu, dan utas utama berlanjut?

Elo
sumber
Agak tidak jelas apa yang ingin Anda capai. Bisakah Anda menambahkan output yang Anda harapkan?
OlegI
1
Metode tesnya sangat jelas - isOK diharapkan benar
Sir Rufo
Tidak ada alasan untuk membuat tugas yang tidak dimulai. Tugas bukan utas, mereka menggunakan utas. Apa yang sedang Anda coba lakukan? Mengapa tidak menggunakan Task.Run()setelah yang pertama Console.WriteLine?
Panagiotis Kanavos
1
@Elo Anda baru saja menggambarkan threadpools. Anda tidak perlu kumpulan tugas untuk mengimplementasikan antrian pekerjaan. Anda memerlukan antrian pekerjaan, mis. Objek Aksi <T>
Panagiotis Kanavos
2
@Elo apa yang Anda coba lakukan sudah tersedia di. NET misalnya melalui kelas TPL Dataflow seperti ActionBlock, atau kelas System.Threading.Channels yang lebih baru. Anda bisa membuat ActionBlock untuk menerima dan memproses pesan menggunakan satu atau lebih tugas bersamaan. Semua blok memiliki buffer input dengan kapasitas yang dapat dikonfigurasi. DOP dan kapasitas memungkinkan Anda untuk mengontrol konkurensi, permintaan throttle, dan menerapkan tekanan balik - jika terlalu banyak pesan yang diantrikan, produsen menunggu
Panagiotis Kanavos

Jawaban:

8

new Task(async () =>

Sebuah tugas tidak mengambil Func<Task>, tetapi sebuah Action. Ini akan memanggil metode asinkron Anda dan mengharapkannya berakhir ketika kembali. Tapi ternyata tidak. Ini mengembalikan tugas. Tugas itu tidak ditunggu oleh tugas baru. Untuk tugas baru, pekerjaan dilakukan setelah metode dikembalikan.

Anda harus menggunakan tugas yang sudah ada alih-alih membungkusnya dengan tugas baru:

[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");
}
tidak ada
sumber
4
Task.Run(async() => ... )juga merupakan pilihan
Sir Rufo
Anda baru saja melakukan hal yang sama sebagai penulis pertanyaan
OlegI
BTW myTask.Start();akan menaikkanInvalidOperationException
Sir Rufo
@OlegI, aku tidak melihatnya. Bisakah Anda menjelaskannya?
novigt
@ nvoigt Saya berasumsi bahwa maksudnya myTask.Start()akan menghasilkan pengecualian untuk alternatifnya dan harus dihapus saat menggunakan Task.Run(...). Tidak ada kesalahan dalam solusi Anda.
404
3

Masalahnya adalah Anda menggunakan kelas non-generik Task, yang tidak dimaksudkan untuk membuahkan hasil. Jadi, ketika Anda membuat Taskinstance lewat delegasi async:

Task myTask = new Task(async () =>

... delegasi diperlakukan sebagai async void. Sebuah async voidbukan Task, itu tidak bisa ditunggu, pengecualiannya tidak dapat ditangani, dan itu adalah sumber dari ribuan pertanyaan yang dibuat oleh programmer yang frustrasi di sini di StackOverflow dan di tempat lain. Solusinya adalah menggunakan Task<TResult>kelas generik , karena Anda ingin mengembalikan hasil, dan hasilnya adalah yang lain Task. Jadi, Anda harus membuat Task<Task>:

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

Sekarang ketika Anda Startbagian luar Task<Task>itu akan selesai hampir seketika karena tugasnya hanya untuk menciptakan bagian dalam Task. Anda kemudian harus menunggu bagian dalam Taskjuga. Ini adalah bagaimana hal itu dapat dilakukan:

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

Anda memiliki dua alternatif. Jika Anda tidak membutuhkan referensi eksplisit ke bagian dalam Taskmaka Anda bisa menunggu bagian luar Task<Task>dua kali:

await await myTask;

... atau Anda dapat menggunakan metode ekstensi bawaan Unwrapyang menggabungkan tugas-tugas luar dan dalam menjadi satu:

await myTask.Unwrap();

Bukaan ini terjadi secara otomatis ketika Anda menggunakan metode yang jauh lebih populer Task.Runyang menciptakan tugas-tugas panas, sehingga metode Unwrapini jarang digunakan saat ini.

Jika Anda memutuskan bahwa delegasi async Anda harus mengembalikan hasil, misalnya a string, maka Anda harus mendeklarasikan myTaskvariabel yang bertipe Task<Task<string>>.

Catatan: Saya tidak menganjurkan penggunaan Taskkonstruktor untuk membuat tugas-tugas dingin. Sebagai praktik yang umumnya disukai, untuk alasan saya tidak benar-benar tahu, tapi mungkin karena jarang digunakan sehingga berpotensi menangkap pengguna / pengelola / pengulas kode yang tidak sadar lainnya karena terkejut.

Saran umum: Hati-hati setiap kali Anda memberikan delegasi async sebagai argumen untuk suatu metode. Metode ini idealnya mengharapkan Func<Task>argumen (artinya memahami delegasi async), atau setidaknya Func<T>argumen (artinya setidaknya yang dihasilkan Tasktidak akan diabaikan). Dalam kasus yang disayangkan bahwa metode ini menerima Action, delegasi Anda akan diperlakukan sebagai async void. Ini jarang yang Anda inginkan, jika pernah.

Theodor Zoulias
sumber
Bagaimana jawaban teknis terperinci! Terima kasih.
Elo
@Senanglah aku!
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");
        }

masukkan deskripsi gambar di sini

BASKA
sumber
3
Anda menyadari bahwa tanpa awaittugas, penundaan tugas tidak akan benar-benar menunda tugas itu, bukan? Anda menghapus fungsionalitas.
novigt
Saya baru saja menguji, @nvoigt benar: Waktu yang berlalu: 0: 00: 00,0106554. Dan kita lihat di tangkapan layar Anda: "waktu yang berlalu: 18 ms", seharusnya> = 1000 ms
Elo
Ya Anda benar, saya memperbarui respons saya. Tnx. Setelah Anda berkomentar saya menyelesaikan ini dengan perubahan minimal. :)
BASKA
1
Ide bagus untuk menggunakan wait () dan tidak menunggu dari dalam Task! terima kasih!
Elo