Apa perbedaan antara Task.Start / Tunggu dan Async / Tunggu?

207

Saya mungkin kehilangan sesuatu tetapi apa perbedaan antara melakukan:

public void MyMethod()
{
  Task t = Task.Factory.StartNew(DoSomethingThatTakesTime);
  t.Wait();
  UpdateLabelToSayItsComplete();
}

public async void MyMethod()
{
  var result = Task.Factory.StartNew(DoSomethingThatTakesTime);
  await result;
  UpdateLabelToSayItsComplete();
}

private void DoSomethingThatTakesTime()
{
  Thread.Sleep(10000);
}
Jon
sumber

Jawaban:

395

Saya mungkin kehilangan sesuatu

Kamu adalah.

apa perbedaan antara melakukan Task.Waitdan await task?

Anda memesan makan siang dari pelayan di restoran. Sesaat setelah memberikan pesanan Anda, seorang teman berjalan masuk dan duduk di sebelah Anda dan memulai percakapan. Sekarang kamu punya dua pilihan. Anda dapat mengabaikan teman Anda sampai tugas selesai - Anda dapat menunggu sampai sup Anda tiba dan tidak melakukan apa pun saat Anda menunggu. Atau Anda dapat menanggapi teman Anda, dan ketika teman Anda berhenti berbicara, pelayan akan membawakan Anda sup.

Task.Waitblok sampai tugas selesai - Anda mengabaikan teman Anda sampai tugas selesai. awaitterus memproses pesan dalam antrian pesan, dan ketika tugas selesai, ia meminta pesan yang mengatakan "ambil tempat Anda tinggalkan setelah itu menunggu". Anda berbicara dengan teman Anda, dan ketika ada jeda dalam percakapan sup tiba.

Eric Lippert
sumber
5
@ronag Tidak, tidak. Bagaimana Anda menyukainya jika menunggu selama Task10 ms akan benar-benar mengeksekusi 10 jam Taskdi utas Anda, sehingga menghalangi Anda selama 10 jam penuh?
svick
62
@ StrugglingCoder: Operator yang menunggu tidak melakukan apa pun kecuali mengevaluasi operan dan kemudian segera mengembalikan tugas ke pemanggil saat ini . Orang-orang mendapatkan ide ini di kepala mereka bahwa asynchrony hanya dapat dicapai melalui pekerjaan pembongkaran ke utas, tetapi itu salah. Anda dapat memasak sarapan dan membaca koran saat roti panggang ada di pemanggang tanpa menyewa juru masak untuk menonton pemanggang. Orang-orang mengatakan bahwa, pasti ada utas - pekerja - yang tersembunyi di dalam pemanggang roti, tetapi saya meyakinkan Anda bahwa jika Anda melihat pemanggang roti, tidak ada lelaki kecil di sana yang menonton roti panggang.
Eric Lippert
11
@ StrugglingCoder: Jadi, siapa yang melakukan pekerjaan yang Anda minta? Mungkin utas lain sedang melakukan pekerjaan, dan utas itu telah ditugaskan ke CPU, sehingga pekerjaan sebenarnya sedang dilakukan. Mungkin pekerjaan sedang dilakukan oleh perangkat keras dan tidak ada utas sama sekali. Tapi tentunya, Anda berkata, harus ada beberapa utas dalam perangkat keras . Tidak. Perangkat keras ada di bawah level utas. Tidak perlu ada utas! Anda mungkin mendapat manfaat dari membaca artikel Stephen Cleary, There Is No Thread.
Eric Lippert
6
@ StrugglingCoder: Sekarang, pertanyaan, misalkan ada pekerjaan asinkron sedang dilakukan dan tidak ada perangkat keras, dan tidak ada utas lainnya. Bagaimana ini mungkin? Nah, anggaplah hal yang Anda tunggu mengantri serangkaian pesan windows , masing-masing yang bekerja sedikit? Sekarang apa yang terjadi? Anda mengembalikan kontrol ke loop pesan, itu mulai menarik pesan keluar dari antrian, melakukan sedikit pekerjaan setiap kali, dan pekerjaan terakhir yang dilakukan adalah "mengeksekusi kelanjutan tugas". Tidak ada utas tambahan!
Eric Lippert
8
@ StrugglingCoder: Sekarang, pikirkan apa yang baru saja saya katakan. Anda sudah tahu bahwa ini adalah cara kerja Windows . Anda menjalankan serangkaian gerakan mouse dan klik tombol dan yang lainnya. Pesan diantrikan, diproses secara bergantian, setiap pesan menyebabkan sejumlah kecil pekerjaan harus dilakukan, dan ketika semuanya selesai, sistem terus berjalan. Async pada satu utas tidak lebih dari yang sudah biasa Anda lakukan: memecah tugas-tugas besar menjadi potongan-potongan kecil, mengantri mereka, dan mengeksekusi semua bit kecil dalam urutan tertentu. Beberapa dari eksekusi tersebut menyebabkan pekerjaan lain harus antri, dan hidup terus berjalan. Satu utas!
Eric Lippert
121

Untuk menunjukkan jawaban Eric di sini ada beberapa kode:

public void ButtonClick(object sender, EventArgs e)
{
  Task t = new Task.Factory.StartNew(DoSomethingThatTakesTime);
  t.Wait();  
  //If you press Button2 now you won't see anything in the console 
  //until this task is complete and then the label will be updated!
  UpdateLabelToSayItsComplete();
}

public async void ButtonClick(object sender, EventArgs e)
{
  var result = Task.Factory.StartNew(DoSomethingThatTakesTime);
  await result;
  //If you press Button2 now you will see stuff in the console and 
  //when the long method returns it will update the label!
  UpdateLabelToSayItsComplete();
}

public void Button_2_Click(object sender, EventArgs e)
{
  Console.WriteLine("Button 2 Clicked");
}

private void DoSomethingThatTakesTime()
{
  Thread.Sleep(10000);
}
Jon
sumber
27
+1 untuk kode (lebih baik dijalankan sekali daripada membaca ratusan kali). Tetapi frasa " //If you press Button2 now you won't see anything in the console until this task is complete and then the label will be updated!" itu menyesatkan. Setelah menekan tombol dengan tombol t.Wait();pada event handler klik ButtonClick(), tidak mungkin untuk menekan apa pun dan kemudian melihat sesuatu di konsol dan memperbarui label "sampai tugas ini selesai" karena GUI dibekukan dan tidak responsif, yaitu klik atau interaksi dengan GUI sedang HILANG sampai selesainya tugas menunggu
Gennady Vanin Геннадий Ванин
2
Saya kira Eric berasumsi bahwa Anda memiliki pemahaman dasar tentang api Tugas. Saya melihat kode itu dan berkata pada diri sendiri, "yup t.Waitakan memblokir di utas utama sampai tugas selesai."
The Muffin Man
50

Contoh ini menunjukkan perbedaannya dengan sangat jelas. Dengan async / menunggu utas panggilan tidak akan memblokir dan melanjutkan eksekusi.

static void Main(string[] args)
{
    WriteOutput("Program Begin");
    // DoAsTask();
    DoAsAsync();
    WriteOutput("Program End");
    Console.ReadLine();
}

static void DoAsTask()
{
    WriteOutput("1 - Starting");
    var t = Task.Factory.StartNew<int>(DoSomethingThatTakesTime);
    WriteOutput("2 - Task started");
    t.Wait();
    WriteOutput("3 - Task completed with result: " + t.Result);
}

static async Task DoAsAsync()
{
    WriteOutput("1 - Starting");
    var t = Task.Factory.StartNew<int>(DoSomethingThatTakesTime);
    WriteOutput("2 - Task started");
    var result = await t;
    WriteOutput("3 - Task completed with result: " + result);
}

static int DoSomethingThatTakesTime()
{
    WriteOutput("A - Started something");
    Thread.Sleep(1000);
    WriteOutput("B - Completed something");
    return 123;
}

static void WriteOutput(string message)
{
    Console.WriteLine("[{0}] {1}", Thread.CurrentThread.ManagedThreadId, message);
}

Output DoAsTask:

[1] Program Mulai
[1] 1 - Mulai
[1] 2 - Tugas dimulai
[3] A - Memulai sesuatu
[3] B - Menyelesaikan sesuatu
[1] 3 - Tugas diselesaikan dengan hasil: 123
[1] Akhir Program

Output DoAsAsync:

[1] Program Mulai
[1] 1 - Mulai
[1] 2 - Tugas dimulai
[3] A - Memulai sesuatu
[1] Akhir Program
[3] B - Menyelesaikan sesuatu
[3] 3 - Tugas diselesaikan dengan hasil: 123

Pembaruan: Contoh yang ditingkatkan dengan menampilkan ID utas di output.

Mas
sumber
4
Tetapi jika saya lakukan: Tugas baru (DoAsTask). Mulai (); alih-alih DoAsAsync (); saya mendapatkan fungsionalitas yang sama, jadi di mana manfaat menunggu ..
omriman12
1
Dengan proposal Anda, hasil tugas harus dievaluasi di tempat lain, mungkin metode lain atau lambda. Async-waiting adalah membuat kode asinkron lebih mudah diikuti. Itu hanya penambah sintaksis.
Mas
@ Apakah saya tidak mengerti mengapa Program End adalah setelah A - Memulai sesuatu. Dari pemahaman saya ketika menunggu proses kata kunci harus segera menuju konteks utama dan kemudian kembali.
@JimmyJimm Dari pemahaman saya Task.Factory.StartNew akan memutar utas baru untuk menjalankan DoSomethingThatTakesTime. Dengan demikian tidak ada jaminan apakah Program Berakhir atau Sesuatu yang Dimulai akan dieksekusi terlebih dahulu.
RiaanDP
@ JimmyJimm: Saya telah memperbarui sampel untuk menampilkan ID utas. Seperti yang Anda lihat, "Program End" dan "A - Memulai sesuatu" berjalan di utas yang berbeda. Jadi sebenarnya urutannya tidak deterministik.
Mas
10

Tunggu (), akan menyebabkan kode async berpotensi dijalankan secara sinkron. menunggu tidak akan.

Misalnya, Anda memiliki aplikasi web asp.net. Panggilan pengguna / getUser / 1 titik akhir. kumpulan aplikasi asp.net akan memilih utas dari utas (Thread1) dan, utas ini akan melakukan panggilan http. Jika Anda Tunggu (), utas ini akan diblokir sampai panggilan http diselesaikan. Saat sedang menunggu, jika UserB memanggil / getUser / 2, maka, kumpulan aplikasi perlu melayani utas lainnya (Thread2) untuk membuat panggilan http lagi. Anda baru saja membuat (Sebenarnya, diambil dari kumpulan aplikasi) utas lainnya tanpa alasan, karena Anda tidak dapat menggunakan Thread1 karena diblokir oleh Tunggu ().

Jika Anda menggunakan menunggu di Thread1, maka, SyncContext akan mengelola sinkronisasi antara panggilan Thread1 dan http. Cukup, ini akan memberi tahu setelah panggilan http selesai. Sementara itu, jika UserB melakukan panggilan / getUser / 2, maka, Anda akan menggunakan Thread1 lagi untuk melakukan panggilan http, karena itu dirilis setelah menunggu ditabrak. Kemudian permintaan lain dapat menggunakannya, bahkan lebih jauh lagi. Setelah panggilan http selesai (user1 atau user2), Thread1 bisa mendapatkan hasilnya dan kembali ke pemanggil (klien). Thread1 digunakan untuk banyak tugas.

Teoman shipahi
sumber
9

Dalam contoh ini, tidak banyak, praktis. Jika Anda sedang menunggu tugas yang kembali pada utas yang berbeda (seperti panggilan WCF) atau melepaskan kontrol ke sistem operasi (seperti File IO), menunggu akan menggunakan lebih sedikit sumber daya sistem dengan tidak memblokir utas.

foson
sumber
3

Pada contoh di atas, Anda dapat menggunakan "TaskCreationOptions.HideScheduler", dan sangat memodifikasi metode "DoAsTask". Metode itu sendiri tidak asinkron, seperti yang terjadi dengan "DoAsAsync" karena mengembalikan nilai "Tugas" dan ditandai sebagai "async", membuat beberapa kombinasi, ini adalah bagaimana ia memberi saya persis sama dengan menggunakan "async / menunggu" :

static Task DoAsTask()
{
    WriteOutput("1 - Starting");
    var t = Task.Factory.StartNew<int>(DoSomethingThatTakesTime, TaskCreationOptions.HideScheduler); //<-- HideScheduler do the magic

    TaskCompletionSource<int> tsc = new TaskCompletionSource<int>();
    t.ContinueWith(tsk => tsc.TrySetResult(tsk.Result)); //<-- Set the result to the created Task

    WriteOutput("2 - Task started");

    tsc.Task.ContinueWith(tsk => WriteOutput("3 - Task completed with result: " + tsk.Result)); //<-- Complete the Task
    return tsc.Task;
}
pengguna8545699
sumber