Apa perbedaan antara pemrograman asinkron dan multithreading?


234

Saya pikir mereka pada dasarnya adalah hal yang sama - menulis program yang membagi tugas antara prosesor (pada mesin yang memiliki 2+ prosesor). Lalu saya membaca ini , yang mengatakan:

Metode Async dimaksudkan sebagai operasi non-pemblokiran. Ekspresi menunggu dalam metode async tidak memblokir utas saat ini sementara tugas yang ditunggu-tunggu sedang berjalan. Sebaliknya, ekspresi mendaftar sisa metode sebagai kelanjutan dan mengembalikan kontrol ke pemanggil metode async.

Async dan menunggu kata kunci tidak menyebabkan utas tambahan dibuat. Metode Async tidak memerlukan multithreading karena metode async tidak berjalan pada utasnya sendiri. Metode ini berjalan pada konteks sinkronisasi saat ini dan menggunakan waktu pada utas hanya ketika metode ini aktif. Anda bisa menggunakan Task.Run untuk memindahkan pekerjaan yang terikat CPU ke utas latar belakang, tetapi utas latar tidak membantu proses yang hanya menunggu hasil tersedia.

dan saya bertanya-tanya apakah seseorang dapat menerjemahkannya ke bahasa Inggris untuk saya. Tampaknya menarik perbedaan antara asyncronicity (apakah itu sebuah kata?) Dan threading dan menyiratkan bahwa Anda dapat memiliki program yang memiliki tugas-tugas yang tidak sinkron tetapi tidak multithreading.

Sekarang saya mengerti ide tugas asinkron seperti contoh pada hal. 467 dari Jon Skeet's C # In Depth, Edisi Ketiga

async void DisplayWebsiteLength ( object sender, EventArgs e )
{
    label.Text = "Fetching ...";
    using ( HttpClient client = new HttpClient() )
    {
        Task<string> task = client.GetStringAsync("http://csharpindepth.com");
        string text = await task;
        label.Text = text.Length.ToString();
    }
}

Kata asynckunci berarti " Fungsi ini, kapan pun dipanggil, tidak akan dipanggil dalam konteks di mana penyelesaiannya diperlukan untuk semua setelah panggilannya dipanggil."

Dengan kata lain, menulis itu di tengah-tengah beberapa tugas

int x = 5; 
DisplayWebsiteLength();
double y = Math.Pow((double)x,2000.0);

, Karena DisplayWebsiteLength()tidak ada hubungannya dengan xatau y, akan menyebabkan DisplayWebsiteLength()dieksekusi "di latar belakang", seperti

                processor 1                |      processor 2
-------------------------------------------------------------------
int x = 5;                                 |  DisplayWebsiteLength()
double y = Math.Pow((double)x,2000.0);     |

Jelas itu contoh yang bodoh, tetapi apakah saya benar atau saya benar-benar bingung atau apa?

(Juga, saya bingung tentang mengapa senderdan etidak pernah digunakan dalam tubuh fungsi di atas.)



senderdan emenyarankan ini sebenarnya adalah pengendali acara - cukup banyak satu-satunya tempat async voidyang diinginkan. Kemungkinan besar, ini disebut pada klik tombol atau sesuatu seperti itu - hasilnya adalah bahwa tindakan ini terjadi sepenuhnya secara tidak sinkron sehubungan dengan sisa aplikasi. Tapi itu semua masih dalam satu utas - utas UI (dengan sepotong kecil waktu pada utas IOCP yang memposting panggilan balik ke utas UI).
Luaan


3
Catatan yang sangat penting pada DisplayWebsiteLengthcontoh kode: Anda tidak boleh menggunakan HttpClientdalam usingpernyataan - Di bawah beban yang berat, kode dapat menghabiskan jumlah soket yang tersedia yang mengakibatkan kesalahan SocketException. Info lebih lanjut tentang Instansiasi yang Tidak Benar .
Gan

1
@ JakubLortz Saya tidak tahu untuk siapa artikel ini sebenarnya. Bukan untuk pemula, karena itu membutuhkan pengetahuan yang baik tentang utas, interupsi, hal-hal yang berhubungan dengan CPU, dll. Tidak untuk pengguna tingkat lanjut, karena bagi mereka semuanya sudah jelas. Saya yakin itu tidak akan membantu siapa pun memahami apa itu semua - terlalu tinggi abstraksi.
Loreno

Jawaban:


589

Kesalahpahaman Anda sangat umum. Banyak orang diajari bahwa multithreading dan asynchrony adalah hal yang sama, tetapi sebenarnya tidak.

Analogi biasanya membantu. Anda sedang memasak di restoran. Pesanan datang untuk telur dan roti panggang.

  • Sinkron: Anda memasak telur, lalu memasak roti panggang.
  • Asinkron, single threaded: Anda memulai memasak telur dan mengatur timer. Anda mulai memasak roti panggang, dan mengatur timer. Sementara mereka berdua memasak, Anda membersihkan dapur. Saat penghitung waktu mati, Anda mengambil telur dari panas dan roti panggang keluar dari pemanggang dan melayani mereka.
  • Asynchronous, multithreaded: Anda menyewa dua koki lagi, satu untuk memasak telur dan satu untuk memasak roti panggang. Sekarang Anda memiliki masalah dalam mengoordinasikan para koki sehingga mereka tidak saling bertentangan di dapur saat berbagi sumber daya. Dan Anda harus membayarnya.

Sekarang apakah masuk akal bahwa multithreading hanya satu jenis asinkron? Threading adalah tentang pekerja; asynchrony adalah tentang tugas . Dalam alur kerja multithreaded Anda menetapkan tugas kepada pekerja. Dalam alur kerja single-threaded asinkron Anda memiliki grafik tugas di mana beberapa tugas bergantung pada hasil yang lain; karena setiap tugas selesai itu memanggil kode yang menjadwalkan tugas berikutnya yang dapat dijalankan, mengingat hasil tugas yang baru saja selesai. Tapi Anda (semoga) hanya membutuhkan satu pekerja untuk melakukan semua tugas, bukan satu pekerja per tugas.

Ini akan membantu untuk menyadari bahwa banyak tugas tidak terikat prosesor. Untuk tugas-tugas yang terikat prosesor, masuk akal untuk merekrut sebanyak mungkin pekerja (utas) karena ada prosesor, tetapkan satu tugas untuk setiap pekerja, tetapkan satu prosesor untuk setiap pekerja, dan mintalah setiap prosesor melakukan pekerjaan tidak lain selain menghitung hasilnya sebagai secepat mungkin. Tetapi untuk tugas yang tidak menunggu prosesor, Anda tidak perlu menugaskan pekerja sama sekali. Anda tinggal menunggu pesan sampai bahwa hasilnya tersedia dan melakukan sesuatu yang lain saat Anda menunggu . Ketika pesan itu tiba maka Anda dapat menjadwalkan kelanjutan dari tugas yang diselesaikan sebagai hal berikutnya pada daftar tugas yang harus Anda selesaikan.

Jadi mari kita lihat contoh Jon lebih detail. Apa yang terjadi?

  • Seseorang memanggil DisplayWebSiteLength. WHO? Kami tidak peduli.
  • Ini menetapkan label, membuat klien, dan meminta klien untuk mengambil sesuatu. Klien mengembalikan objek yang mewakili tugas mengambil sesuatu. Tugas itu sedang berlangsung.
  • Apakah sedang berlangsung di utas lain? Mungkin tidak. Baca artikel Stephen tentang mengapa tidak ada utas.
  • Sekarang kita menunggu tugas. Apa yang terjadi? Kami memeriksa untuk melihat apakah tugas telah selesai antara waktu kami membuatnya dan kami menunggu. Jika ya, maka kami mengambil hasilnya dan terus berjalan. Anggap saja itu belum selesai. Kami mendaftar sisa metode ini sebagai kelanjutan dari tugas itu dan kembali .
  • Sekarang kontrol telah kembali ke penelepon. Apa fungsinya? Apapun yang diinginkannya.
  • Sekarang anggaplah tugas selesai. Bagaimana itu bisa terjadi? Mungkin itu berjalan di utas lainnya, atau mungkin penelepon yang baru saja kita kembali untuk membiarkannya berjalan sampai selesai pada utas saat ini. Apapun, kami sekarang memiliki tugas yang selesai.
  • Tugas yang diselesaikan meminta utas yang benar - lagi, kemungkinan satu - satunya utas - untuk menjalankan kelanjutan tugas.
  • Kontrol lewat segera kembali ke metode yang baru saja kita tinggalkan di titik menunggu. Sekarang ada adalah hasilnya tersedia sehingga kita dapat menetapkan textdan menjalankan sisa metode.

Seperti analogi saya. Seseorang meminta Anda untuk sebuah dokumen. Anda mengirim surat untuk dokumen, dan terus melakukan pekerjaan lain. Ketika tiba di surat Anda diberi sinyal, dan ketika Anda merasa seperti itu, Anda melakukan sisa alur kerja - buka amplop, bayar biaya pengiriman, apa pun. Anda tidak perlu mempekerjakan pekerja lain untuk melakukan semua itu untuk Anda.


8
@ user5648283: Perangkat keras adalah level yang salah untuk memikirkan tugas. Tugas hanyalah sebuah objek yang (1) menyatakan bahwa nilai akan tersedia di masa mendatang dan (2) dapat menjalankan kode (pada utas yang benar) ketika nilai itu tersedia . Bagaimana tugas individu apa pun mendapatkan hasilnya di masa depan tergantung pada itu. Beberapa akan menggunakan perangkat keras khusus seperti "disk" dan "kartu jaringan" untuk melakukan itu; beberapa akan menggunakan perangkat keras seperti CPU.
Eric Lippert

13
@ user5648283: Sekali lagi, pikirkan analogi saya. Ketika seseorang meminta Anda untuk memasak telur dan roti panggang, Anda menggunakan perangkat keras khusus - kompor dan pemanggang roti - dan Anda dapat membersihkan dapur saat perangkat keras melakukan tugasnya. Jika seseorang meminta Anda untuk telur, roti bakar, dan kritik asli dari film Hobbit terakhir, Anda dapat menulis ulasan Anda saat telur dan roti panggang sedang dimasak, tetapi Anda tidak perlu menggunakan perangkat keras untuk itu.
Eric Lippert

9
@ user5648283: Sekarang untuk pertanyaan Anda tentang "mengatur ulang kode", pertimbangkan ini. Misalkan Anda memiliki metode P yang memiliki pengembalian hasil, dan metode Q yang melakukan pendahuluan atas hasil P. Langkah melalui kode. Anda akan melihat bahwa kami menjalankan sedikit Q lalu sedikit P lalu sedikit Q ... Apakah Anda mengerti maksudnya? Menunggu pada dasarnya menghasilkan pengembalian dalam kostum . Sekarang apakah lebih jelas?
Eric Lippert

10
Pemanggang adalah perangkat keras. Perangkat keras tidak memerlukan utas untuk melayaninya; disk dan kartu jaringan dan yang lainnya berjalan pada level yang jauh di bawah thread OS.
Eric Lippert

5
@ShivprasadKoirala: Itu sama sekali tidak benar sama sekali . Jika Anda percaya itu, maka Anda memiliki beberapa keyakinan yang sangat keliru tentang asinkron . Inti dari asynchrony di C # adalah ia tidak membuat utas.
Eric Lippert

27

Javascript dalam peramban adalah contoh yang bagus dari program asinkron yang tidak memiliki utas.

Anda tidak perlu khawatir tentang beberapa keping kode yang menyentuh objek yang sama secara bersamaan: setiap fungsi akan selesai berjalan sebelum javascript lain diizinkan berjalan di halaman.

Namun, ketika melakukan sesuatu seperti permintaan AJAX, tidak ada kode yang berjalan sama sekali, sehingga javascript lain dapat merespons hal-hal seperti peristiwa klik hingga permintaan itu kembali dan memanggil panggilan balik yang terkait dengannya. Jika salah satu dari penangan acara ini masih berjalan ketika permintaan AJAX kembali, penangannya tidak akan dipanggil sampai selesai. Hanya ada satu "utas" JavaScript yang berjalan, meskipun mungkin bagi Anda untuk secara efektif menjeda hal yang Anda lakukan sampai Anda memiliki informasi yang Anda butuhkan.

Dalam aplikasi C #, hal yang sama terjadi setiap kali Anda berurusan dengan elemen UI - Anda hanya diizinkan berinteraksi dengan elemen UI saat Anda berada di utas UI. Jika pengguna mengklik tombol, dan Anda ingin merespons dengan membaca file besar dari disk, seorang programmer yang tidak berpengalaman mungkin membuat kesalahan dengan membaca file dalam event handler klik itu sendiri, yang akan menyebabkan aplikasi "membeku" sampai file selesai dimuat karena tidak diizinkan menanggapi klik, melayang, atau peristiwa terkait UI lainnya apa pun hingga utas tersebut dibebaskan.

Salah satu opsi yang mungkin digunakan oleh pemrogram untuk menghindari masalah ini adalah membuat utas baru untuk memuat file, dan kemudian memberi tahu kode utas itu bahwa ketika file dimuat, perlu menjalankan kode yang tersisa pada utas UI lagi sehingga dapat memperbarui elemen UI berdasarkan apa yang ditemukan dalam file. Sampai baru-baru ini, pendekatan ini sangat populer karena itu adalah apa yang dipermudah oleh perpustakaan C # dan bahasa, tetapi pada dasarnya lebih rumit daripada yang seharusnya.

Jika Anda berpikir tentang apa yang dilakukan CPU ketika membaca file di tingkat perangkat keras dan Sistem Operasi, itu pada dasarnya mengeluarkan instruksi untuk membaca potongan data dari disk ke dalam memori, dan mengenai sistem operasi dengan "interupsi". "Saat pembacaan selesai. Dengan kata lain, membaca dari disk (atau I / O benar-benar) adalah operasi yang asinkron secara inheren . Konsep utas menunggu I / O untuk menyelesaikan adalah abstraksi yang dibuat oleh pengembang perpustakaan untuk membuatnya lebih mudah untuk diprogram. Itu tidak perlu.

Sekarang, sebagian besar operasi I / O di .NET memiliki ...Async()metode yang sesuai Anda dapat memanggil, yang mengembalikan Taskhampir segera. Anda dapat menambahkan panggilan balik ke ini Taskuntuk menentukan kode yang ingin Anda jalankan ketika operasi asinkron selesai. Anda juga dapat menentukan utas mana yang Anda inginkan untuk menjalankan kode itu, dan Anda dapat memberikan token yang dapat diperiksa oleh operasi asinkron dari waktu ke waktu untuk melihat apakah Anda memutuskan untuk membatalkan tugas asinkron, memberikannya kesempatan untuk menghentikan kerjanya dengan cepat dan dengan anggun.

Sampai async/awaitkata kunci ditambahkan, C # jauh lebih jelas tentang bagaimana kode panggilan balik dipanggil, karena panggilan balik itu dalam bentuk delegasi yang Anda kaitkan dengan tugas. Agar tetap memberi Anda manfaat menggunakan ...Async()operasi, sambil menghindari kerumitan dalam kode, async/awaitabstraksi dari penciptaan delegasi tersebut. Tapi mereka masih ada di kode yang dikompilasi.

Jadi Anda dapat memiliki event handler UI Anda awaitoperasi I / O, membebaskan utas UI untuk melakukan hal-hal lain, dan lebih atau kurang otomatis kembali ke utas UI setelah Anda selesai membaca file - tanpa harus buat utas baru.


Hanya ada satu "thread" JavaScript yang berjalan - tidak lagi benar dengan Pekerja Web .
oleksii

6
@oleksii: Itu benar secara teknis, tetapi saya tidak akan membahasnya karena API Pekerja Web itu sendiri tidak sinkron, dan Pekerja Web tidak diizinkan untuk secara langsung memengaruhi nilai javascript atau DOM pada halaman web yang mereka gunakan dari, yang berarti paragraf kedua yang krusial dari jawaban ini masih berlaku. Dari perspektif programmer, ada sedikit perbedaan antara memohon Pekerja Web dan memohon permintaan AJAX.
StriplingWarrior
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.