Чому відповідальність за виклик відповідає за забезпечення безпеки потоку в програмуванні GUI?


37

Я бачив, в багатьох місцях, що канонічна мудрість 1 полягає в тому, що відповідальність за абонентом повинен бути впевненим, що ви знаходитесь в потоці інтерфейсу користувача під час оновлення компонентів інтерфейсу користувача (зокрема, в Java Swing, що ви перебуваєте в Dispatch Thread Event ) .

Чому це так? Нитка розсилки подій викликає занепокоєння з точки зору MVC / MVP / MVVM; обробляти його в будь-якому місці, однак вигляд створює тісний зв'язок між реалізацією подання та моделем реалізації цього перегляду.

Зокрема, скажімо, у мене є архітектурний додаток MVC, який використовує Swing. Якщо абонент несе відповідальність за оновлення компонентів на потоці відправки події, то, якщо я спробую замінити свою реалізацію Swing View для реалізації JavaFX, я повинен змінити весь код Presenter / Controller, щоб замість цього використовувати нитку додатка JavaFX .

Отже, я маю два питання:

  1. Чому відповідальність за абонент відповідає за забезпечення безпеки потоку компонентів інтерфейсу? Де недолік у моїх міркуваннях вище?
  2. Як я можу спроектувати свою заявку таким чином, щоб вона зв'язалася з безпекою щодо безпеки різьби, але все ще була належним чином захищена від потоку?

Дозвольте мені додати трохи коду Java MCVE, щоб проілюструвати, що я маю на увазі під "відповідальним телефоном" (тут є деякі інші хороші практики, яких я не роблю, але я навмисно намагаюся бути якомога меншим):

Відповідальний абонент:

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());
  }
}

Переглянути відповідальність:

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: Автор цієї публікації має найвищий бал тегів у Swing on Overflow. Він каже, що це повсюдно, і я також бачив, що це відповідальність за абонента в інших місцях.


1
Е, виступ ІМХО. Ці публікації подій не надходять безкоштовно, і в нетривіальних додатках ви хочете мінімізувати їх кількість (і переконайтеся, що жодна не є занадто великою), але мінімізація / конденсація повинна логічно проводитися в презентаторі.
Звичайний

1
@Ordous Ви все ще можете гарантувати, що розміщення буде мінімальним під час розміщення передачі потоку у поле перегляду.
durron597

2
Деякий час тому я прочитав справді хороший блог, в якому обговорювали цю проблему, і в основному це говорить про те, що намагатися зробити безпеку нитки набору інтерфейсу користувача дуже небезпечно, оскільки це вводить можливі тупики та залежно від того, як це реалізовано, умови перегонів у рамки. Також є врахування ефективності. Зараз не так багато, але коли Swing був вперше випущений, його піддавали сильній критиці за його продуктивність (це було погано), але це насправді не було виною Swing, тому що люди не знали, як ним користуватися.
MadProgrammer

1
SWT застосовує концепцію безпеки потоку, викидаючи винятки, якщо ви їх порушуєте, не дуже, але принаймні вам про це відомо. Ви згадуєте про перехід від Swing до JavaFX, але у вас виникне ця проблема практично з будь-якою рамкою інтерфейсу, Swing, здається, є тією, яка висвітлює проблему. Ви можете створити проміжний рівень (контролер контролера?), Завданням якого є забезпечення правильної синхронізації дзвінків до інтерфейсу. Неможливо точно знати, як ви могли б спроектувати ваші API, що не
користуються

1
і більшість розробників скаржиться, що будь-який захист потоку, реалізований в API інтерфейсу, був обмежувальним або не відповідав їхнім потребам. Краще, щоб ви могли вирішити, як ви хочете вирішити це питання, виходячи з ваших потреб
MadProgrammer

Відповіді:


22

Під кінець свого невдалого нарису мрії Грехем Гамільтон (провідний архітектор Java) зазначає, що розробники "хочуть зберегти еквівалентність з моделлю черги подій, їм потрібно буде дотримуватися різних не очевидних правил" і мати видиме і явне Модель черги подій "начебто допомагає людям надійніше наслідувати цю модель і таким чином побудувати програми GUI, які надійно працюють".

Іншими словами, якщо ви спробуєте поставити багатопоточний фасад поверх моделі черги подій, абстракція періодично просочиться неочевидними способами, які надзвичайно важко налагодити. Схоже, це буде працювати на папері, але закінчується розпадом виробництва.

Додавання невеликих обгортків навколо окремих компонентів, ймовірно, не буде проблематичним, як-от оновити смужку прогресу з робочої нитки. Якщо ви спробуєте зробити щось більш складне, що вимагає декількох блокувань, починати стає важко міркувати про те, як взаємодіють багатопотоковий шар і шар черги подій.

Зауважте, що подібні випуски є універсальними для всіх наборів інструментів GUI. Якщо припустити, що модель диспетчеризації подій у вашому презентаторі / контролері не є тісно пов'язаною з вами лише однієї конкретної моделі інструментарію графічного інструментарію, вона з'єднує вас із усіма ними . Інтерфейс черги подій не повинен бути таким важким для абстрактного.


25

Оскільки безпека нитки лінійки GUI є масовою головним болем і вузьким місцем.

Потік управління в графічних інтерфейсах часто проходить у двох напрямках від черги подій до кореневого вікна до віджетів gui та від коду програми до віджета, розповсюдженого до кореневого вікна.

Розробити стратегію блокування, яка не блокує кореневе вікно (викличе багато суперечок) складно . Блокування знизу вгору, а інша нитка блокується зверху вниз - це чудовий спосіб досягти миттєвого глухого кута.

І перевірка того, чи поточний потік є потоком gui, кожен раз коштує дорого і може викликати плутанину щодо того, що насправді відбувається з gui, особливо коли ви виконуєте послідовність запису читання оновлення. Для уникнення перегонів потрібно мати блокування даних.


1
Цікаво, що я вирішую цю проблему, маючи структуру черги для цих оновлень, яку я написав, що керує передачею потоку.
durron597

2
@ durron597: І у вас ніколи не буде оновлень, залежно від поточного стану інтерфейсу, який може вплинути інший потік? Тоді це може спрацювати.
Дедуплікатор

Навіщо вам потрібні вкладені замки? Чому потрібно блокувати все кореневе вікно під час роботи над деталями у дочірньому вікні? Порядок блокування є вирішуваною проблемою, надаючи єдиний підданий спосіб блокування непомітних замків у правильному порядку (зверху вниз або знизу вгору, але не залишайте вибір виклику)
MSalters

1
@MSalters з root я мав на увазі поточне вікно. Щоб отримати всі блоки, які вам потрібно придбати, вам потрібно підійти до ієрархії, яка вимагає блокування кожного контейнера, коли ви стикаєтеся з ним, отримуючи батьківський і розблоковуючи (щоб переконатися, що ви блокуєте лише зверху вниз), а потім сподіватися, що він не змінився на після отримання кореневого вікна ви робите блокування зверху вниз.
храповик виродка

@ratchetfreak: Якщо дитина, яку ви намагаєтесь заблокувати, буде видалена іншою ниткою під час її блокування, це просто прикро, але не має значення для блокування. Ви просто не можете працювати з об'єктом, який вилучив інший потік. Але чому це інші нитки для видалення об'єктів / вікон, які ваша нитка все ще використовує? Це не добре в будь-якому сценарії, не тільки в інтерфейсі користувача.
MSalters

17

Ниткорізність (у моделі загальної пам’яті) - властивість, яка прагне протистояти зусиллям з абстракції. Простий приклад є Set-тип: в той час як Contains(..)і Add(...)і Update(...)це цілком допустимо API в однотрідових сценаріях, сценарієм багато-потрібен AddOrUpdate.

Те ж саме стосується інтерфейсу користувача - якщо ви хочете відобразити список елементів із кількістю підручних елементів у списку, вам потрібно оновлювати обидва зміни.

  1. Інструментарій не може вирішити цю проблему, оскільки блокування не гарантує, що порядок операцій залишається правильним.
  2. Перегляд може вирішити проблему, але лише якщо ви дозволите діловому правилу, що число, розташоване вгорі списку, повинно відповідати кількості елементів у списку та оновити список лише через перегляд. Не зовсім те, яким повинен бути MVC.
  3. Ведучий може вирішити це, але повинен усвідомлювати, що вигляд має особливі потреби щодо нитки.
  4. Прив’язка даних до моделі з багатопотоковою здатністю - ще один варіант. Але це ускладнює модель із тими речами, які повинні викликати інтерфейс.

Жодне з них не виглядає дуже спокусливо. Примусити ведучого до відповідальності за обробку ниток рекомендується не тому, що це добре, а тому, що він працює, а альтернативи - гірші.


Можливо, між представленням та презентатором може бути введений шар, який також можна буде замінити.
durron597

2
.NET має System.Windows.Threading.Dispatcherобробку диспетчеризації як на WPF, так і на WinForms UI-Threads. Такий шар між ведучим та переглядом безумовно корисний. Але це лише забезпечує незалежність інструментарію, а не незалежність.
Патрік

9

Деякий час тому я прочитав справді хороший блог, в якому обговорювали цю проблему (згадував Карл Білефельдт), але в основному йдеться про те, що намагатися зробити безпеку нитки набору інтерфейсу користувача дуже небезпечно, оскільки це вводить можливі тупикові місця і залежно від того, як це реалізовані, перегони умови в рамках.

Також є врахування ефективності. Зараз не так багато, але коли Swing був вперше випущений, його піддавали сильній критиці за його продуктивність (це було погано), але це насправді не було виною Swing, тому що люди не знали, як ним користуватися.

SWT застосовує концепцію безпеки потоку, викидаючи винятки, якщо ви їх порушуєте, не дуже, але принаймні вам про це відомо.

Якщо ви заглянете в процес фарбування, наприклад, порядок, в якому елементи фарбуються, дуже важливий. Ви не хочете, щоб фарбування одного компонента побічно впливало на будь-яку іншу частину екрану. Уявіть собі, якби ви могли оновити властивість тексту мітки, але вона була намальована двома різними потоками, ви могли б отримати зіпсований вихід. Таким чином, вся фарбування виконується в межах однієї нитки, як правило, виходячи з порядку вимог / запитів (але іноді стискається, щоб зменшити кількість фактичних фізичних циклів фарби)

Ви згадуєте про перехід від Swing до JavaFX, але у вас виникне ця проблема практично з будь-якою рамкою інтерфейсу користувача (не тільки з товстими клієнтами, але і з Інтернетом), Swing, здається, саме той, що висвітлює проблему.

Ви можете розробити проміжний рівень (контролер контролера?), Завданням якого є забезпечення правильної синхронізації дзвінків до інтерфейсу. Неможливо точно знати, як ви могли б спроектувати ваші не-інтерфейси частини API з точки зору API інтерфейсу, і більшість розробників скаржиться, що будь-який захист потоку, реалізований в API інтерфейсу, повинен бути обмежувальним або не відповідає їх потребам. Краще, щоб ви могли вирішити, як ви хочете вирішити це питання, виходячи з ваших потреб

Одне з найбільших питань, яке потрібно врахувати, - це можливість обґрунтувати заданий порядок подій на основі відомих даних. Наприклад, якщо користувач змінює розмір вікна, модель черги подій гарантує, що буде задано порядок подій, це може здатися простим, але якщо черга дозволила подіям викликати інші потоки, ви більше не можете гарантувати замовлення в які події можуть статися (умова перегонів) і раптом вам доведеться почати турбуватися про різні стани, а не робити одне, поки щось інше не станеться, і ви почнете ділитися державними прапорами навколо, і ви закінчите спагетті.

Гаразд, ви можете вирішити це, маючи якусь чергу, яка впорядковувала події залежно від часу їх видачі, але чи не це ми вже маємо? Крім того, ви все ще не можете гарантувати, що потік B буде генерувати події ПІСЛЯ потоку A

Основна причина, чому люди турбуються про необхідність думати про свій код, полягає в тому, що вони змушені думати про свій код / ​​дизайн. "Чому не може бути простіше?" Це не може бути простішим, адже це не проста проблема.

Я пам'ятаю, коли PS3 був випущений, і Sony розговорилася процесором Cell, і це можливість виконувати окремі лінії логіки, декодувати аудіо, відео, завантажувати та вирішувати дані моделі. Один розробник гри запитав: "Це все приголомшливо, але як синхронізувати потоки?"

Проблема, про яку говорив розробник, полягає в тому, що в якийсь момент всі ці окремі потоки повинні бути синхронізовані до однієї труби для виведення. Бідний ведучий просто знизав плечима, оскільки це не було питання, з яким вони були знайомі. Очевидно, у них були рішення, щоб вирішити цю проблему зараз, але це було смішно на той час.

Сучасні комп’ютери одночасно беруть багато інформації з безлічі різних місць, все це потрібно обробляти та доставляти користувачеві далеко, що не заважає представленню іншої інформації, тому це складна проблема, поза єдине просте рішення.

Тепер, маючи можливість перемикати рамки, це непроста річ, яку можна розробити, АЛЕ, візьміть MVC на мить, MVC може бути багатошаровим, тобто у вас може бути MVC, який займається безпосередньо управлінням фреймворком інтерфейсу, ви Тоді можна було б обговорити це, знову ж таки, у вищому шарі MVC, який займається взаємодією з іншими (потенційно багатопотоковими) рамками, відповідальність за цей шар визначатиме, як повідомляється / оновлюється нижній шар MVC.

Потім ви будете використовувати кодування для побудови моделей інтерфейсу та фабричних або будівельних моделей для побудови цих різних шарів. Це означає, що ви багатопотокові рамки від'єднуєтесь від шару інтерфейсу через використання середнього шару як ідеї.


2
У вас не було б цієї проблеми з Інтернетом. У JavaScript навмисно немає підтримки для введення ниток - час виконання JavaScript є лише однією великою чергою подій. (Так, я знаю, що строго кажучи JS має WebWorkers - це нервові нитки, і поводяться більше, як актори іншими мовами).
James_pic

1
@James_pic Те, що у вас є насправді, є частиною браузера, який виконує функції синхронізатора черги подій, і це, в основному, те, про що ми говорили, абонент відповідав за те, щоб відбувалися оновлення з чергою подій інструментаріїв
MadProgrammer

Так, саме. Ключова відмінність в Інтернеті полягає в тому, що ця синхронізація відбувається незалежно від коду абонента, оскільки час виконання не забезпечує механізму, який би дозволяв виконувати код за межами черги подій. Тож абоненту не потрібно брати на себе відповідальність за це. Я вважаю, що це також значна частина мотивації розвитку NodeJS - якщо середовище виконання - це цикл подій, то весь код є циклом подій за замовчуванням.
James_pic

1
Однак, є рамки інтерфейсу браузера, які мають власну подію-цикл-в-цикл-подія (я дивлюся на вас кутовий). Оскільки ці рамки вже не захищені під час виконання, абоненти повинні також забезпечити виконання коду в циклі подій, подібно до багатопотокового коду в інших кадрах.
James_pic

Чи буде якась особлива проблема з тим, що елементи керування "лише для дисплея" забезпечують безпеку потоку автоматично? Якщо в одному редакторі є текстовий елемент керування, який може бути записаний одним потоком і прочитаний іншим, немає жодного хорошого способу зробити так, щоб запис було видно потоком, не синхронізувавши хоча б одну з цих дій у фактичний стан управління елементів управління, але чи важливі будь-які подібні проблеми для елементів керування лише на дисплеї? Я б подумав, що ті можуть легко забезпечити необхідне блокування або блокування для операцій, що виконуються над ними, і дозволити абоненту ігнорувати терміни, коли ...
supercat
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.