Mengapa Anda pernah 'menunggu' suatu metode, dan kemudian segera menginterogasi nilai pengembaliannya?

24

Dalam artikel MSDN ini , kode contoh berikut disediakan (sedikit diedit untuk singkatnya):

public async Task<ActionResult> Details(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }

    Department department = await db.Departments.FindAsync(id);

    if (department == null)
    {
        return HttpNotFound();
    }

    return View(department);
}

The FindAsyncMetode mengambil sebuah Departmentobjek dengan ID-nya, dan mengembalikan Task<Department>. Kemudian departemen segera diperiksa untuk melihat apakah itu nol. Seperti yang saya pahami, meminta nilai Task dengan cara ini akan memblokir eksekusi kode hingga nilai dari metode yang ditunggu dikembalikan, secara efektif menjadikan ini panggilan sinkron.

Mengapa kamu melakukan ini? Bukankah lebih mudah untuk hanya memanggil metode sinkron Find(id), jika Anda akan segera memblokirnya?

Robert Harvey
sumber
Bisa jadi terkait implementasi. ... else return null;Maka Anda perlu memeriksa bahwa metode tersebut benar-benar menemukan departemen yang Anda minta.
Jeremy Kato
Saya tidak melihat apa pun di asp.net, tetapi di aplikasi destop, dengan melakukannya dengan cara ini Anda tidak membekukan ui
Rémi
Berikut ini tautan yang menjelaskan konsep tunggu dari perancang ... msdn.microsoft.com/en-us/magazine/hh456401.aspx
Jon Raynor
Menunggu hanya perlu dipikirkan dengan ASP.NET jika saklar kontak ulir memperlambat Anda, atau penggunaan memori dari banyak tumpukan ulir merupakan masalah bagi Anda.
Ian

Jawaban:

24

Seperti yang saya pahami, meminta nilai Task dengan cara ini akan memblokir eksekusi kode hingga nilai dari metode yang ditunggu dikembalikan, secara efektif menjadikan ini panggilan sinkron.

Tidak terlalu.

Ketika Anda memanggil await db.Departments.FindAsync(id)tugas dikirim dan utas saat ini dikembalikan ke kolam untuk digunakan oleh operasi lain. Alur eksekusi diblokir (karena itu akan terlepas dari menggunakan departmentsegera setelah, jika saya memahami hal-hal dengan benar), tetapi utas itu sendiri bebas untuk digunakan oleh hal-hal lain saat Anda menunggu operasi selesai dari mesin (dan ditandai oleh suatu peristiwa atau port penyelesaian).

Jika Anda menelepon d.Departments.Find(id)maka utasnya duduk di sana dan menunggu tanggapan, meskipun sebagian besar pemrosesan sedang dilakukan pada DB.

Anda secara efektif membebaskan sumber daya CPU saat disk terikat.

Telastyn
sumber
2
Tapi saya pikir semua yang awaitdilakukan adalah menandatangani sisa metode sebagai kelanjutan dari utas yang sama (ada pengecualian; beberapa metode asink memutar benang mereka sendiri), atau menandatangani asyncmetode sebagai kelanjutan pada utas yang sama dan memungkinkan kode yang tersisa untuk dieksekusi (seperti yang Anda lihat, saya tidak jelas tentang cara asynckerjanya). Apa yang Anda gambarkan terdengar seperti bentuk canggihThread.Sleep(untilReturnValueAvailable)
Robert Harvey
1
@RobertHarvey - ia menetapkannya sebagai kelanjutan, tetapi setelah Anda mengirim tugas (dan kelanjutannya) untuk diproses, tidak ada yang tersisa untuk dijalankan. Ini tidak dijamin berada di utas yang sama kecuali Anda menentukannya (via ConfigureAwaitiirc).
Telastyn
1
Lihat jawaban saya ... kelanjutan adalah marshalled kembali ke utas asli secara default.
Michael Brown
2
Saya rasa saya melihat apa yang saya lewatkan di sini. Agar hal ini memberikan manfaat apa pun, kerangka kerja ASP.NET MVC harus awaitdiserukan public async Task<ActionResult> Details(int? id). Jika tidak, panggilan asli hanya akan diblokir, menunggu untuk department == nulldiselesaikan.
Robert Harvey
2
@RobertHarvey Pada saat await ..."kembali" FindAsyncpanggilan telah selesai. Itu yang menunggu. Ini disebut menunggu karena itu membuat kode Anda menunggu. (Tetapi perhatikan bahwa ini tidak sama dengan membuat utas saat ini menunggu hal-hal)
user253751
17

Saya benar-benar benci karena tidak ada contoh yang menunjukkan bagaimana mungkin menunggu beberapa baris sebelum menunggu tugas. Pertimbangkan ini.

Foo foo = await getFoo();
Bar bar = await getBar();

Console.WriteLine(“Do some other stuff to prepare.”);

doStuff(foo, bar);

Ini adalah jenis kode yang didukung contoh, dan Anda benar. Ada sedikit akal dalam hal ini. Itu membebaskan utas utama untuk melakukan hal-hal lain, seperti menanggapi input UI, tetapi kekuatan sebenarnya dari async / menunggu adalah saya dapat dengan mudah terus melakukan hal-hal lain sementara saya sedang menunggu tugas yang berpotensi berjalan lama untuk diselesaikan. Kode di atas akan "memblokir" dan menunggu untuk mengeksekusi garis cetak sampai kita mendapatkan Foo & Bar. Tidak perlu menunggu. Kita bisa memprosesnya sambil menunggu.

Task<Foo> foo = getFoo();
Task<Bar> bar = getBar();

Console.WriteLine(“Do some other stuff to prepare.”);

doStuff(await foo, await bar);

Sekarang, dengan kode yang ditulis ulang, kita tidak berhenti dan menunggu nilai-nilai kita sampai kita harus. Saya selalu mencari peluang seperti ini. Menjadi pintar saat kita menunggu dapat menyebabkan peningkatan kinerja yang signifikan. Kami punya beberapa core hari ini, mungkin lebih baik menggunakannya.

Bebek karet
sumber
1
Hm, itu kurang tentang inti / utas dan lebih lanjut tentang menggunakan panggilan asinkron dengan benar.
Deduplicator
Anda tidak salah @Dupuplikator. Itu sebabnya saya mengutip "blokir" dalam jawaban saya. Sulit untuk berbicara tentang hal ini tanpa menyebutkan utas, tetapi sementara masih mengakui mungkin ada atau mungkin tidak ada multi-threading yang terlibat.
RubberDuck
Saya sarankan Anda berhati-hati dengan menunggu di dalam panggilan metode lain .. Kadang-kadang kompiler salah paham menunggu ini dan Anda mendapatkan pengecualian yang sangat aneh. Setelah semua async / menunggu hanya sintaksis gula, Anda dapat memeriksa dengan sharplab.io bagaimana kode yang dihasilkan terlihat. Saya mengamati ini pada beberapa kesempatan dan sekarang saya hanya menunggu satu baris di atas panggilan di mana hasilnya diperlukan ... tidak perlu sakit kepala itu.
diusir
"Tidak ada artinya dalam hal ini." - Ada banyak akal dalam hal ini. Kasus-kasus di mana "terus melakukan hal-hal lain" berlaku dalam metode yang sama tidak umum; jauh lebih umum bahwa Anda hanya ingin awaitdan membiarkan utas melakukan hal yang sama sekali berbeda.
Sebastian Redl
Ketika saya mengatakan "ada sedikit akal dalam hal ini" @SebastianRedl, saya maksudkan kasus di mana Anda jelas bisa menjalankan kedua tugas secara paralel daripada menjalankan, tunggu, jalankan, tunggu. Ini jauh lebih umum daripada yang Anda kira. Saya yakin jika Anda melihat-lihat basis kode Anda, Anda akan menemukan peluang.
RubberDuck
6

Jadi ada lagi yang terjadi di balik layar di sini. Async / Await adalah gula sintaksis. Pertama-tama lihat tanda tangan dari fungsi FindAsync. Ini mengembalikan Tugas. Sudah Anda melihat keajaiban kata kunci, itu membuka kotak Tugas itu menjadi Departemen.

Fungsi panggilan tidak menghalangi. Apa yang terjadi adalah bahwa penugasan ke departemen dan segala sesuatu yang mengikuti kata kunci tunggu akan dimasukkan ke dalam penutupan dan untuk semua maksud dan tujuan diteruskan ke metode Task.ContinueWith (fungsi FindAsync secara otomatis dieksekusi pada utas yang berbeda).

Tentu saja ada lebih banyak yang terjadi di balik layar karena operasi diatur kembali ke utas asli (sehingga Anda tidak perlu lagi khawatir tentang sinkronisasi dengan UI saat melakukan operasi latar belakang) dan dalam kasus fungsi panggilan menjadi Async ( dan dipanggil secara tidak sinkron) hal yang sama terjadi di stack.

Jadi yang terjadi adalah Anda mendapatkan keajaiban operasi Async, tanpa jebakan.

Michael Brown
sumber
1

Tidak, itu tidak segera kembali. Menunggu membuat panggilan metode asinkron. Ketika FindAsync dipanggil, metode Detail kembali dengan tugas yang belum selesai. Ketika FindAsync selesai, itu akan mengembalikan hasilnya ke variabel departemen dan melanjutkan sisa metode Detail.

Steve
sumber
Tidak, tidak ada pemblokiran sama sekali. Tidak akan pernah sampai ke departemen == null sampai setelah metode async selesai.
Steve
1
async awaitumumnya tidak membuat utas baru, dan bahkan jika itu terjadi, Anda masih perlu menunggu nilai departemen itu untuk mengetahui apakah itu nol.
Robert Harvey
2
Jadi pada dasarnya apa yang Anda katakan adalah bahwa, agar ini bermanfaat, panggilan untuk public async Task<ActionResult> juga harus diubah await.
Robert Harvey
2
@ RobertTarvey Ya, Anda punya idenya. Async / Menunggu pada dasarnya adalah virus. Jika Anda "menunggu" satu fungsi, Anda juga harus menunggu fungsi yang memanggilnya. awaittidak boleh dicampur dengan .Wait()atau .Result, karena ini dapat menyebabkan kebuntuan. Rantai async / menunggu biasanya berakhir pada fungsi dengan async voidtanda tangan, yang sebagian besar digunakan untuk penangan acara, atau untuk fungsi yang dipanggil langsung oleh elemen UI.
KChaloux
1
@Steve - " ... jadi itu akan di utas sendiri. " Tidak, baca Tugas masih bukan utas dan async tidak paralel . Ini termasuk hasil aktual yang menunjukkan bahwa utas baru tidak dibuat.
ToolmakerSteve
1

Saya suka berpikir "async" seperti kontrak, kontrak yang mengatakan "saya bisa menjalankan ini secara tidak sinkron jika Anda membutuhkannya, tetapi Anda dapat memanggil saya seperti fungsi sinkron lainnya juga".

Artinya, satu pengembang membuat fungsi dan beberapa keputusan desain membuat mereka membuat / menandai banyak fungsi sebagai "async". Penelepon / konsumen fungsi memiliki kebebasan untuk menggunakannya saat mereka memutuskan. Seperti yang Anda katakan, Anda dapat memanggil menunggu sebelum panggilan fungsi dan menunggu, dengan cara ini Anda memperlakukannya seperti fungsi sinkron, tetapi jika Anda ingin, Anda dapat memanggilnya tanpa menunggu sebagai

Task<Department> deptTask = db.Departments.FindAsync(id);

dan setelah, katakanlah, 10 baris ke bawah fungsi yang Anda panggil

Department d = await deptTask;

karenanya memperlakukannya sebagai fungsi asinkron.

Terserah kamu.

pengguna734028
sumber
1
Ini lebih seperti "Saya berhak untuk menangani pesan secara tidak sinkron, gunakan ini untuk mendapatkan hasilnya".
Deduplicator
-1

"Jika Anda akan segera memblokirnya," jawabnya adalah "Ya". Hanya ketika Anda membutuhkan respons cepat, tunggu / async masuk akal. Misalnya utas UI datang ke metode yang benar-benar asinkron, utas UI akan kembali dan terus mendengarkan klik tombol, sementara kode di bawah "menunggu" akan dikecualikan oleh utas lain dan akhirnya mendapatkan hasilnya.

pengguna10200952
sumber
1
Apa yang dijawab oleh jawaban ini dan jawaban yang lain tidak?