Jalankan dua tugas asinkron secara paralel dan kumpulkan hasil di .NET 4.5

116

Saya sudah mencoba beberapa lama untuk mendapatkan sesuatu yang saya pikir akan mudah bekerja dengan .NET 4.5

Saya ingin menjalankan dua tugas yang berjalan lama pada waktu yang sama dan mengumpulkan
hasilnya dengan cara terbaik C # 4.5 (RTM)

Berikut ini berfungsi tetapi saya tidak menyukainya karena:

  • Saya ingin Sleepmenjadi metode async sehingga bisa awaitmetode lain
  • Itu hanya terlihat kikuk Task.Run()
  • Saya tidak berpikir ini bahkan menggunakan fitur bahasa baru sama sekali!

Kode kerja:

public static void Go()
{
    Console.WriteLine("Starting");

    var task1 = Task.Run(() => Sleep(5000));    
    var task2 = Task.Run(() => Sleep(3000));

    int totalSlept = task1.Result + task2.Result;

    Console.WriteLine("Slept for a total of " + totalSlept + " ms");
}

private static int Sleep(int ms)
{
    Console.WriteLine("Sleeping for " + ms);
    Thread.Sleep(ms);
    Console.WriteLine("Sleeping for " + ms + " FINISHED");
    return ms;
}

Kode tidak berfungsi:

Pembaruan: Ini benar-benar berfungsi dan merupakan cara yang benar untuk melakukannya, satu-satunya masalah adalah file Thread.Sleep

Kode ini tidak berfungsi karena panggilan untuk Sleep(5000)segera memulai tugas yang berjalan sehingga Sleep(1000)tidak berjalan hingga selesai. Hal ini benar meskipun Sleepadalah asyncdan aku tidak menggunakan awaitatau menelepon .Resultterlalu cepat.

Saya pikir mungkin ada cara untuk mendapatkan non-running Task<T>dengan memanggil asyncmetode sehingga saya kemudian dapat memanggil Start()dua tugas, tetapi saya tidak tahu bagaimana cara mendapatkan Task<T>dari memanggil metode async.

public static void Go()
{
    Console.WriteLine("Starting");

    var task1 = Sleep(5000);    // blocks
    var task2 = Sleep(1000);

    int totalSlept = task1.Result + task2.Result;

    Console.WriteLine("Slept for " + totalSlept + " ms");
}

private static async Task<int> Sleep(int ms)
{
    Console.WriteLine("Sleeping for " + ms);
    Thread.Sleep(ms);
    return ms;
}
Simon_Weaver
sumber
catatan: menjadikan Go metode asinkron tidak ada bedanya
Simon_Weaver
3
Pemblokiran terjadi di task1.Resultbukan var task1 = Sleep(5000)karena metode Sleep Anda tanpa menunggu kata kunci sinkron.
Arvis

Jawaban:

86

Anda harus menggunakan Task.Delay alih-alih Sleep untuk pemrograman async dan kemudian gunakan Task.WhenAll untuk menggabungkan hasil tugas. Tugas akan berjalan paralel.

public class Program
    {
        static void Main(string[] args)
        {
            Go();
        }
        public static void Go()
        {
            GoAsync();
            Console.ReadLine();
        }
        public static async void GoAsync()
        {

            Console.WriteLine("Starting");

            var task1 = Sleep(5000);
            var task2 = Sleep(3000);

            int[] result = await Task.WhenAll(task1, task2);

            Console.WriteLine("Slept for a total of " + result.Sum() + " ms");

        }

        private async static Task<int> Sleep(int ms)
        {
            Console.WriteLine("Sleeping for {0} at {1}", ms, Environment.TickCount);
            await Task.Delay(ms);
            Console.WriteLine("Sleeping for {0} finished at {1}", ms, Environment.TickCount);
            return ms;
        }
    }
softveda
sumber
11
Ini adalah jawaban yang bagus ... tetapi saya pikir ini adalah jawaban yang salah sampai saya menjalankannya. lalu saya mengerti. Itu benar-benar dijalankan dalam 5 detik. Triknya adalah TIDAK menunggu tugas dengan segera, alih-alih menunggu di Task.WhenAll.
Tim Lovell-Smith
113
async Task<int> LongTask1() { 
  ...
  return 0; 
}

async Task<int> LongTask2() { 
  ...
  return 1; 
}

...
{
   Task<int> t1 = LongTask1();
   Task<int> t2 = LongTask2();
   await Task.WhenAll(t1,t2);
   //now we have t1.Result and t2.Result
}
Bart
sumber
2
Saya +1 karena Anda menyatakan t1, t2 sebagai Tugas, yang merupakan cara yang benar.
Minime
12
Saya yakin solusi ini membutuhkan metode Go untuk juga menjadi async, yang berarti ia menunjukkan kemampuan untuk menjadi asynchronous. Jika Anda menginginkan sesuatu yang lebih seperti kasus penanya di mana metode pemanggil Gosinkron, tetapi ingin menyelesaikan dua tugas independen secara asinkron (yaitu tidak perlu menyelesaikan sebelum yang lain, tetapi keduanya harus diselesaikan sebelum eksekusi dilanjutkan) maka Task.WaitAllakan lebih baik, dan Anda tidak Tidak memerlukan kata kunci await, sehingga tidak memerlukan Gometode pemanggilan untuk menjadi asinkron itu sendiri. Tidak ada pendekatan yang lebih baik, hanya masalah apa tujuan Anda.
AaronLS
1
Metode async void LongTask1() {...}kosong : tidak memiliki properti Task.Result. Gunakan Task tanpa T dalam kasus seperti: async Task LongTask1().
Arvis
Saya tidak mendapatkan hasil dari salah satu tugas. Jadi saya mengubahnya menjadi Task<TResult> t1 = LongTask1();dan sekarang saya mengerti t1.Result. <TResult>adalah jenis pengembalian hasil Anda. Anda akan memerlukan return <TResult>metode Anda agar ini berfungsi.
gilu
1
Mungkin perlu disebutkan bahwa jika Anda melakukan beberapa hal yang sangat sederhana dan tidak menginginkan tambahan t1dan t2variabel, Anda dapat menggunakan new Task(...). Sebagai contoh: int int1 = 0; await Task.WhenAll(new Task(async () => { int1 = await LongTask1(); }));. Satu tangkapan dari pendekatan ini adalah bahwa kompilator tidak akan mengenali bahwa variabel itu ditugaskan dan akan memperlakukannya sebagai tidak ditetapkan jika Anda tidak memberikannya nilai awal.
Robert Dennis
3

Meskipun Sleepmetode Anda adalah asinkron, Thread.Sleepbukan. Ide keseluruhan dari async adalah menggunakan kembali satu utas, bukan memulai banyak utas. Karena Anda telah memblokir menggunakan panggilan sinkron ke Thread.Sleep, itu tidak akan berfungsi.

Saya berasumsi bahwa itu Thread.Sleepadalah penyederhanaan dari apa yang sebenarnya ingin Anda lakukan. Dapatkah penerapan Anda yang sebenarnya diberi kode sebagai metode asinkron?

Jika Anda memang perlu menjalankan beberapa panggilan pemblokiran sinkron, saya pikir mencari di tempat lain!

Richard
sumber
terima kasih Richard - ya sepertinya berfungsi seperti yang diharapkan ketika saya benar-benar menggunakan panggilan layanan saya
Simon_Weaver
lalu bagaimana cara menjalankan async? Saya memiliki aplikasi yang melakukan banyak perpindahan file dan menunggu file, sekitar 5 detik, dan kemudian proses lain, ketika saya "ketika untuk semua" itu pertama kali dijalankan pertama, lalu kedua, meskipun saya berkata:, var x = y()dan belum var x=await y()atau y().wait()masih itu tunggu dulu, dan jika async tidak menanganinya dengan sendirinya, apa yang harus saya lakukan? CATATAN bahwa y didekorasi dengan async, dan saya berharap itu melakukan semua dalam waktu semua, tidak tepat di tempat itu ditetapkan, EDIT: saya hanya ketika pasangan saya berkata, mari kita coba Task.Factory, dan dia mengatakan itu berhasil ketika saya keluar sisi kelas ini
deadManN
2

Untuk menjawab poin ini:

Saya ingin Sleep menjadi metode async sehingga dapat menunggu metode lain

Anda mungkin bisa menulis ulang Sleepfungsinya seperti ini:

private static async Task<int> Sleep(int ms)
{
    Console.WriteLine("Sleeping for " + ms);
    var task = Task.Run(() => Thread.Sleep(ms));
    await task;
    Console.WriteLine("Sleeping for " + ms + "END");
    return ms;
}

static void Main(string[] args)
{
    Console.WriteLine("Starting");

    var task1 = Sleep(2000);
    var task2 = Sleep(1000);

    int totalSlept = task1.Result +task2.Result;

    Console.WriteLine("Slept for " + totalSlept + " ms");
    Console.ReadKey();
}

menjalankan kode ini akan menghasilkan:

Starting
Sleeping for 2000
Sleeping for 1000
*(one second later)*
Sleeping for 1000END
*(one second later)*
Sleeping for 2000END
Slept for 3000 ms
asidis
sumber
2

Sekarang akhir pekan !

    public async void Go()
    {
        Console.WriteLine("Start fosterage...");

        var t1 = Sleep(5000, "Kevin");
        var t2 = Sleep(3000, "Jerry");
        var result = await Task.WhenAll(t1, t2);

        Console.WriteLine($"My precious spare time last for only {result.Max()}ms");
        Console.WriteLine("Press any key and take same beer...");
        Console.ReadKey();
    }

    private static async Task<int> Sleep(int ms, string name)
    {
            Console.WriteLine($"{name} going to sleep for {ms}ms :)");
            await Task.Delay(ms);
            Console.WriteLine("${name} waked up after {ms}ms :(";
            return ms;
    }
Arvis
sumber
0

Artikel ini membantu menjelaskan banyak hal. Ini dalam gaya FAQ.

FAQ Async / Await

Bagian ini menjelaskan mengapa Thread.Sleepberjalan pada utas asli yang sama - yang menyebabkan kebingungan awal saya.

Apakah kata kunci "async" menyebabkan pemanggilan metode antri ke ThreadPool? Untuk membuat utas baru? Untuk meluncurkan kapal roket ke Mars?

Tidak, tidak, dan tidak. Lihat pertanyaan sebelumnya. Kata kunci "async" menunjukkan kepada compiler bahwa "await" dapat digunakan di dalam metode, sehingga metode tersebut dapat ditangguhkan pada titik tunggu dan eksekusinya dilanjutkan secara asinkron saat instance yang ditunggu selesai. Inilah sebabnya mengapa kompilator mengeluarkan peringatan jika tidak ada "menunggu" di dalam metode yang ditandai sebagai "async".

Simon_Weaver
sumber