Mengapa tanggung jawab penelepon untuk memastikan keamanan thread dalam pemrograman GUI?

37

Saya telah melihat, di banyak tempat, bahwa itu adalah kebijaksanaan kanonik 1 bahwa itu adalah tanggung jawab penelepon untuk memastikan Anda berada di utas UI saat memperbarui komponen UI (khususnya, di Java Swing, bahwa Anda berada pada Utas Pengiriman Utas ) .

Kenapa begitu? Thread Pengiriman Acara adalah perhatian dari pandangan di MVC / MVP / MVVM; untuk menanganinya di mana saja tetapi tampilan menciptakan hubungan yang erat antara implementasi tampilan, dan model threading dari implementasi tampilan itu.

Secara khusus, katakanlah saya memiliki aplikasi arsitek MVC yang menggunakan Swing. Jika penelepon bertanggung jawab untuk memperbarui komponen pada Event Dispatch Thread, maka jika saya mencoba untuk menukar implementasi Swing View saya untuk implementasi JavaFX, saya harus mengubah semua kode Presenter / Controller untuk menggunakan utas JavaFX Application .

Jadi, saya kira saya punya dua pertanyaan:

  1. Mengapa penelepon bertanggung jawab untuk memastikan keamanan utas komponen UI? Di mana kesalahan dalam alasan saya di atas?
  2. Bagaimana saya bisa mendesain aplikasi saya agar tidak memiliki sambungan yang longgar dari masalah keamanan ulir ini, namun tetap aman untuk ulir?

Izinkan saya menambahkan beberapa kode Java MCVE untuk mengilustrasikan apa yang saya maksud dengan "penelepon bertanggung jawab" (ada beberapa praktik baik lainnya di sini yang tidak saya lakukan tetapi saya mencoba dengan tujuan seminimal mungkin):

Penelepon bertanggung jawab:

public class Presenter {
  private final View;

  void updateViewWithNewData(final Data data) {
    EventQueue.invokeLater(new Runnable() {
      public void run() {
        view.setData(data);
      }
    });
  }
}
public class View {
  void setData(Data data) {
    component.setText(data.getMessage());
  }
}

Lihat yang bertanggung jawab:

public class Presenter {
  private final View;

  void updateViewWithNewData(final Data data) {
    view.setData(data);
  }
}
public class View {
  void setData(Data data) {
    EventQueue.invokeLater(new Runnable() {
      public void run() {
        component.setText(data.getMessage());
      }
    });
  }
}

1: Penulis postingan itu memiliki skor tag tertinggi di Swing on Stack Overflow. Dia mengatakan ini di semua tempat dan saya juga melihatnya sebagai tanggung jawab penelepon di tempat lain juga.

durron597
sumber
1
Eh, kinerja IMHO. Posting acara tersebut tidak datang secara gratis, dan di aplikasi non-sepele Anda ingin meminimalkan jumlahnya (dan pastikan tidak ada yang terlalu besar), tetapi meminimalkan / kondensasi harus dilakukan secara logis di presenter.
Ordous
1
@Ordous Anda masih dapat memastikan bahwa postingan minimal sambil meletakkan thread handoff di tampilan.
durron597
2
Saya membaca blog yang sangat bagus beberapa waktu lalu yang membahas masalah ini, yang pada dasarnya dikatakan adalah bahwa sangat berbahaya untuk mencoba dan membuat utas kit UI aman, karena memperkenalkan kemungkinan deadlock dan tergantung pada bagaimana implementasinya, kondisi lomba ke dalam kerangka. Ada juga pertimbangan kinerja. Tidak begitu banyak sekarang, tetapi ketika Swing pertama kali dirilis itu sangat dikritik karena kinerjanya (buruk), tapi itu tidak benar-benar kesalahan Swing, itu adalah kurangnya pengetahuan orang-orang tentang bagaimana menggunakannya.
MadProgrammer
1
SWT memberlakukan konsep keselamatan ulir dengan melemparkan pengecualian jika Anda melanggarnya, tidak cantik, tapi setidaknya Anda dibuat menyadarinya. Anda menyebutkan perubahan dari Swing ke JavaFX, tetapi Anda akan memiliki masalah ini dengan hampir semua kerangka UI, Swing sepertinya menjadi salah satu yang menyoroti masalah. Anda bisa mendesain lapisan menengah (pengontrol?) Yang tugasnya memastikan bahwa panggilan ke UI disinkronkan dengan benar. Mustahil untuk mengetahui dengan tepat bagaimana Anda bisa mendesain bagian non-UI API Anda dari perspektif API UI
MadProgrammer
1
dan sebagian besar pengembang akan mengeluh bahwa perlindungan utas apa pun yang diterapkan ke API UI adalah untuk membatasi atau tidak memenuhi kebutuhan mereka. Lebih baik untuk memungkinkan Anda memutuskan bagaimana Anda ingin menyelesaikan masalah ini, berdasarkan kebutuhan Anda
MadProgrammer

Jawaban:

22

Menjelang akhir esai impiannya yang gagal , Graham Hamilton (arsitek utama Jawa) menyebutkan jika pengembang "ingin menjaga kesetaraan dengan model antrian acara, mereka harus mengikuti berbagai aturan yang tidak jelas," dan memiliki aturan yang tidak jelas, "dan memiliki yang terlihat dan eksplisit. model antrian acara "tampaknya membantu orang untuk lebih andal mengikuti model dan dengan demikian membangun program GUI yang bekerja dengan andal."

Dengan kata lain, jika Anda mencoba untuk meletakkan fasad multithreaded di atas model antrian acara, abstraksi itu kadang-kadang akan bocor dengan cara yang tidak jelas yang sangat sulit untuk di-debug. Sepertinya itu akan berfungsi di atas kertas, tetapi akhirnya berantakan dalam produksi.

Menambahkan pembungkus kecil di sekitar komponen tunggal mungkin tidak akan bermasalah, seperti memperbarui bilah kemajuan dari utas pekerja. Jika Anda mencoba untuk melakukan sesuatu yang lebih kompleks yang memerlukan banyak kunci, itu mulai menjadi sangat sulit untuk alasan tentang bagaimana lapisan multithreaded dan lapisan peristiwa antrian berinteraksi.

Perhatikan bahwa masalah seperti ini bersifat universal untuk semua toolkit GUI. Menganggap model pengiriman acara di presenter / controller Anda tidak secara ketat menghubungkan Anda ke hanya satu model konkurensi GUI toolkit, itu menyatukan Anda dengan mereka semua . Antarmuka antrian acara tidak terlalu sulit untuk diabstraksi.

Karl Bielefeldt
sumber
25

Karena membuat lib thread GUI aman adalah sakit kepala besar dan hambatan.

Kontrol aliran di GUI sering berjalan dalam 2 arah dari antrian acara ke jendela root ke widget gui dan dari kode aplikasi ke widget yang disebarkan ke jendela root.

Merancang strategi penguncian yang tidak mengunci jendela root (akan menyebabkan banyak pertengkaran) sulit . Mengunci dari bawah ke atas saat utas lainnya mengunci dari atas ke bawah adalah cara yang bagus untuk mencapai kebuntuan instan.

Dan memeriksa apakah utas saat ini adalah utas gui setiap kali mahal dan dapat menyebabkan kebingungan tentang apa yang sebenarnya terjadi dengan gui, terutama ketika Anda melakukan urutan baca pembaruan tulis. Itu perlu memiliki kunci pada data untuk menghindari balapan.

ratchet freak
sumber
1
Menariknya, saya memecahkan masalah ini dengan memiliki kerangka antrian untuk pembaruan ini yang saya tulis yang mengelola handoff utas.
durron597
2
@ durron597: Dan Anda tidak pernah memiliki pembaruan tergantung pada keadaan UI saat ini utas lain mungkin mempengaruhi? Maka itu mungkin berhasil.
Deduplicator
Mengapa Anda perlu kunci bersarang? Mengapa Anda perlu mengunci seluruh jendela root saat mengerjakan detail di jendela anak? Kunci pesanan adalah masalah yang dapat dipecahkan dengan menyediakan metode terbuka untuk mengunci kunci banyak orang dalam urutan yang benar (baik dari atas ke bawah atau dari bawah ke atas, tetapi jangan meninggalkan pilihan kepada penelepon)
MSalters
1
@Salin dengan root yang saya maksud adalah jendela saat ini. Untuk mendapatkan semua kunci yang perlu Anda peroleh, Anda perlu menapaki hierarki yang mengharuskan Anda mengunci setiap wadah saat Anda menjumpai mendapatkan induk dan membuka kunci (untuk memastikan Anda hanya mengunci top-down) dan kemudian berharap itu tidak berubah pada Anda setelah Anda mendapatkan jendela root dan Anda melakukan kunci top-down.
ratchet freak
@ratchetfreak: Jika anak yang Anda coba kunci dihapus oleh utas lain saat Anda menguncinya, itu hanya sedikit disayangkan tetapi tidak relevan dengan penguncian. Anda tidak bisa beroperasi pada objek yang thread lain baru saja dihapus. Tetapi mengapa utas lain menghapus objek / jendela yang masih digunakan utas Anda? Itu tidak baik dalam skenario apa pun, tidak hanya UI.
MSalters
17

Threadedness (dalam model memori bersama) adalah properti yang cenderung menentang upaya abstraksi. Contoh sederhana adalah Set-type: while Contains(..)dan Add(...)dan Update(...)merupakan API yang benar-benar valid dalam skenario berulir tunggal, skenario multi-berulir memerlukan a AddOrUpdate.

Hal yang sama berlaku untuk UI - jika Anda ingin menampilkan daftar item dengan jumlah item di atas daftar, Anda perlu memperbarui keduanya pada setiap perubahan.

  1. Toolkit tidak dapat menyelesaikan masalah itu karena penguncian tidak memastikan urutan operasi tetap benar.
  2. Tampilan dapat menyelesaikan masalah, tetapi hanya jika Anda mengizinkan aturan bisnis bahwa nomor di atas daftar harus cocok dengan jumlah item dalam daftar dan hanya memperbarui daftar melalui tampilan. Tidak persis seperti apa MVC seharusnya.
  3. Presenter dapat menyelesaikannya, tetapi perlu menyadari bahwa pandangan memiliki kebutuhan khusus berkaitan dengan threading.
  4. Penyatuan data ke model yang mampu multi-threading adalah pilihan lain. Tapi itu menyulitkan model dengan hal-hal yang seharusnya menjadi perhatian UI.

Tidak ada yang terlihat menggoda. Membuat presenter bertanggung jawab untuk menangani threading dianjurkan bukan karena itu baik, tetapi karena berfungsi dan alternatifnya lebih buruk.

Patrick
sumber
Mungkin sebuah layer bisa diperkenalkan di antara View dan Presenter yang juga bisa ditukar.
durron597
2
NET memiliki. System.Windows.Threading.DispatcherUntuk menangani pengiriman ke WPF dan WinForms UI-Threads. Lapisan semacam itu antara presenter dan view pasti bermanfaat. Tapi itu hanya menyediakan kebebasan toolkit, bukan kebebasan threading.
Patrick
9

Saya membaca blog yang sangat bagus beberapa waktu lalu yang membahas masalah ini (disebutkan oleh Karl Bielefeldt), yang pada dasarnya dikatakan adalah bahwa sangat berbahaya untuk mencoba dan membuat utas kit UI aman, karena memperkenalkan kebuntuan yang mungkin terjadi dan tergantung pada bagaimana hal itu terjadi. Diimplementasikan, perlombaan kondisi ke dalam kerangka kerja.

Ada juga pertimbangan kinerja. Tidak begitu banyak sekarang, tetapi ketika Swing pertama kali dirilis itu sangat dikritik karena kinerjanya (buruk), tapi itu tidak benar-benar kesalahan Swing, itu adalah kurangnya pengetahuan orang-orang tentang bagaimana menggunakannya.

SWT memberlakukan konsep keselamatan ulir dengan melemparkan pengecualian jika Anda melanggarnya, tidak cantik, tapi setidaknya Anda dibuat menyadarinya.

Jika Anda melihat proses melukis, misalnya, urutan unsur-unsur yang dilukis sangat penting. Anda tidak ingin lukisan satu komponen memiliki efek samping pada bagian lain dari layar. Bayangkan jika Anda dapat memperbarui properti teks label, tetapi itu dilukis oleh dua utas yang berbeda, Anda bisa berakhir dengan output yang rusak. Jadi semua lukisan dikerjakan dalam satu utas, biasanya berdasarkan urutan persyaratan / permintaan (tapi terkadang terkondensasi untuk mengurangi jumlah siklus cat fisik yang sebenarnya)

Anda menyebutkan perubahan dari Swing ke JavaFX, tetapi Anda akan memiliki masalah ini dengan hampir semua kerangka UI (bukan hanya klien yang tebal, tetapi juga web), Swing sepertinya menjadi satu-satunya yang menyoroti masalah tersebut.

Anda bisa mendesain lapisan menengah (pengontrol dari controller?) Yang tugasnya adalah memastikan bahwa panggilan ke UI disinkronkan dengan benar. Tidak mungkin untuk mengetahui dengan tepat bagaimana Anda dapat merancang bagian non-UI API Anda dari perspektif API UI dan sebagian besar pengembang akan mengeluh bahwa perlindungan utas apa pun yang diterapkan ke dalam API UI adalah untuk membatasi atau tidak memenuhi kebutuhan mereka. Lebih baik memungkinkan Anda untuk memutuskan bagaimana Anda ingin menyelesaikan masalah ini, berdasarkan kebutuhan Anda

Salah satu masalah terbesar yang perlu Anda pertimbangkan adalah kemampuan untuk membenarkan urutan peristiwa tertentu, berdasarkan masukan yang diketahui. Misalnya, jika pengguna mengubah ukuran jendela, model Antrian Acara menjamin bahwa urutan peristiwa tertentu akan terjadi, ini mungkin tampak sederhana, tetapi jika antrian memungkinkan acara dipicu oleh utas lain, maka Anda tidak lagi dapat menjamin pesanan dalam di mana peristiwa mungkin terjadi (kondisi balapan) dan tiba-tiba Anda harus mulai mengkhawatirkan keadaan yang berbeda dan tidak melakukan satu hal sampai sesuatu yang lain terjadi dan Anda mulai harus berbagi bendera negara di sekitar dan Anda berakhir dengan spageti.

Oke, Anda bisa menyelesaikan ini dengan memiliki semacam antrian yang memesan acara berdasarkan waktu mereka dikeluarkan, tetapi bukankah itu yang sudah kita miliki? Selain itu, Anda masih tidak dapat menjamin bahwa utas B akan menghasilkan acara itu SETELAH utas A

Alasan utama orang marah karena harus memikirkan kode mereka, adalah karena mereka dibuat untuk berpikir tentang kode / desain mereka. "Kenapa tidak bisa lebih sederhana?" Itu tidak bisa lebih sederhana, karena itu bukan masalah yang sederhana.

Saya ingat ketika PS3 dirilis dan Sony berbicara tentang prosesor Cell dan kemampuannya untuk melakukan jalur logika yang terpisah, mendekode audio, video, memuat dan menyelesaikan data model. Salah satu pengembang game bertanya, "Itu semua luar biasa, tetapi bagaimana Anda menyinkronkan streaming?"

Masalah yang dibicarakan pengembang adalah, pada titik tertentu, semua aliran terpisah perlu disinkronkan ke satu pipa untuk keluaran. Presenter yang malang itu hanya mengangkat bahu karena itu bukan masalah yang mereka kenal. Jelas, mereka memiliki solusi untuk menyelesaikan masalah ini sekarang, tetapi itu lucu pada saat itu.

Komputer modern mengambil banyak input dari banyak tempat berbeda secara bersamaan, semua input itu perlu diproses dan dikirim ke pengguna secara langsung yang tidak mengganggu penyajian informasi lain, jadi ini adalah masalah yang kompleks, tanpa solusi tunggal yang sederhana.

Sekarang, memiliki kemampuan untuk beralih kerangka kerja, itu bukan hal yang mudah untuk dirancang untuk, TETAPI, ambil MVC sejenak, MVC dapat berlapis-lapis, yaitu, Anda dapat memiliki MVC yang berhubungan langsung dengan mengelola kerangka kerja UI, Anda kemudian dapat membungkusnya, sekali lagi, dalam lapisan MVC yang lebih tinggi yang berhubungan dengan interaksi dengan kerangka kerja lain (berpotensi multi-ulir), akan menjadi tanggung jawab lapisan ini untuk menentukan bagaimana lapisan MVC yang lebih rendah diberitahukan / diperbarui.

Anda kemudian akan menggunakan pengkodean untuk pola desain antarmuka dan pola pabrik atau pembangun untuk membangun lapisan yang berbeda ini. Ini berarti, bahwa Anda kerangka kerja multithreaded menjadi dipisahkan dari lapisan UI melalui penggunaan lapisan tengah, sebagai ide.

Program Mad
sumber
2
Anda tidak akan memiliki masalah dengan web ini. JavaScript sengaja tidak memiliki dukungan threading - JavaScript runtime secara efektif hanya satu antrian acara besar. (Ya, saya tahu bahwa JS yang tegas memiliki WebWorkers - ini adalah utas yang kacau, dan berperilaku lebih seperti aktor dalam bahasa lain).
James_pic
1
@James_pic Apa yang sebenarnya Anda miliki adalah bagian dari browser yang bertindak sebagai sinkronisasi untuk antrian acara, yang pada dasarnya adalah apa yang kami bicarakan, pemanggil bertanggung jawab untuk memastikan bahwa pembaruan terjadi dengan antrian acara toolkit
MadProgrammer
Ya persis. Perbedaan utama, dalam konteks web, adalah bahwa sinkronisasi ini terjadi terlepas dari kode pemanggil, karena runtime tidak menyediakan mekanisme yang akan memungkinkan eksekusi kode di luar antrian acara. Jadi penelepon tidak perlu bertanggung jawab untuk itu. Saya percaya itu juga merupakan bagian besar dari motivasi di balik pengembangan NodeJS - jika lingkungan runtime adalah loop peristiwa, maka semua kode adalah loop peristiwa yang disadari secara default.
James_pic
1
Yang mengatakan, ada kerangka UI browser yang memiliki event-loop-dalam-an-event-loop mereka sendiri (saya melihat Anda Angular). Karena kerangka kerja ini tidak dilindungi lagi oleh runtime, penelepon juga harus memastikan kode dieksekusi dalam loop peristiwa, mirip dengan kode multithreaded dalam kerangka kerja lain.
James_pic
Apakah akan ada masalah tertentu dengan memiliki kontrol "hanya-display" yang memberikan keamanan thread secara otomatis? Jika seseorang memiliki kontrol teks yang dapat diedit yang kebetulan ditulis oleh satu utas dan dibaca oleh yang lain, tidak ada cara yang baik seseorang dapat membuat tulisan dapat dilihat oleh utas tanpa menyinkronkan setidaknya satu dari tindakan tersebut ke keadaan kontrol UI yang sebenarnya, tetapi apakah masalah seperti itu penting untuk kontrol tampilan saja? Saya akan berpikir mereka dapat dengan mudah memberikan penguncian yang diperlukan atau interlock untuk operasi yang dilakukan pada mereka, dan memungkinkan penelepon mengabaikan waktu ketika ...
supercat