Статьи

Как безопасно использовать дисплей SWT asyncExec

Большинство наборов инструментов пользовательского интерфейса являются однопоточными, и SWT не является исключением. Это означает, что доступ к объектам пользовательского интерфейса должен осуществляться исключительно из одного потока, так называемого потока пользовательского интерфейса. С другой стороны, долгосрочные задачи должны выполняться в фоновых потоках, чтобы пользовательский интерфейс реагировал. Это заставляет фоновые потоки ставить в очередь обновления для выполнения в потоке пользовательского интерфейса вместо непосредственного доступа к объектам пользовательского интерфейса.

Чтобы запланировать выполнение кода в потоке пользовательского интерфейса, SWT предлагает методы Display asyncE‌xec () и syncE‌xec () .

Отображение asyncE‌xec против syncE‌xec

Хотя оба метода ставят в очередь аргумент для выполнения в потоке пользовательского интерфейса, они отличаются тем, что они делают впоследствии (или не делают). Как следует из названия, asyncE‌xec () работает асинхронно. Он возвращается сразу после того, как исполняемый файл был поставлен в очередь, и не ожидает его выполнения. Принимая во внимание, что syncE‌xec () блокирует и, таким образом, ожидает, пока код не будет выполнен.

Как правило, используйте asyncE‌xec (), если вы не зависите от результата запланированного кода, например, просто обновляете виджеты, чтобы сообщить о прогрессе. Если запланированный код возвращает что-то релевантное для дальнейшего потока управления — например, запрашивает ввод в диалоговом окне блокировки — тогда я бы выбрал syncE‌xec ().

Если, например, фоновый поток хочет сообщить о проделанной работе, простейшая форма может выглядеть так:

1
2
3
4
5
progressBar.getDisplay().asyncE‌xec( new Runnable() {
  public void r‌un() {
    progressBar.setSelection( ticksWorked );
  }
} );

asyncE‌xec () планирует выполнение, которое будет выполнено в потоке пользовательского интерфейса «при следующей разумной возможности» (как говорит JavaDoc).

К сожалению, приведенный выше код, скорее всего, будет время от времени вызывать исключение из виджета или, точнее, из-за исключения SWTE с кодом == SWT.ERROR_WIDGET_DISPOSED.

Поэтому причина в том, что индикатор выполнения может больше не существовать при обращении к нему (т.е. вызывается setSelection ()). Хотя у нас все еще есть ссылка на виджет, он не очень полезен, так как сам виджет расположен. Решение очевидно: код должен сначала проверить, существует ли виджет, прежде чем работать с ним:

1
2
3
4
5
6
7
progressBar.getDisplay().asyncE‌xec( new Runnable() {
  public void r‌un() {
    if( !progressBar.isDisposed() ) {
      progressBar.setSelection( workDone );
    }
  }
} );

Как бы это ни казалось очевидным, утомительным является повторное выполнение такой проверки. Возможно, вы захотите поискать в «Eclipse bugzilla» «уничтоженный виджет», чтобы понять, насколько часто эта проблема возникает. Поэтому мы извлекли вспомогательный класс, который инкапсулирует проверку

1
2
3
4
5
new UIThreadSynchronizer().asyncE‌xec( progressBar, new Runnable() {
    public void r‌un() {
      progressBar.setSelection( workDone );
    }
  } );

Метод UIThreadSynchronizer asyncE‌xec () ожидает виджет в качестве первого параметра, который служит контекстом . Контекстный виджет предназначен для того, чтобы быть виджетом, на который повлиял бы запускаемый или подходящий родительский виджет, если затронуто более одного виджета. Непосредственно перед выполнением runnable проверяется виджет контекста. Если он все еще жив (то есть не удален), код будет выполнен, в противном случае код будет молча отброшен. Хотя поведение по игнорированию кода для удаленных виджетов может показаться небрежным, оно работало для всех ситуаций, которые мы до сих пор допускали.

Код модульного тестирования, который обеспечивает межпотоковое взаимодействие, особенно сложно протестировать. Следовательно, UIThreadSynchronizer — хотя он и не имеет состояния — должен быть создан для замены через тестовое дублирование .

Хотя в примерах используется asncE‌xec (), UIThreadSynchronizer также поддерживает syncE‌xec (). И, конечно же, вспомогательный класс также совместим с RAP / RWT .

Если вы внимательно прочитаете исходный код, вы, возможно, заметили, что возможны условия гонки. Поскольку ни один из методов класса Widget не предназначен для работы с потоками, значение, возвращаемое isDisposed () или getDisplay (), может быть устаревшим (см. Строку 51 и строку 60 ). Это намеренно игнорируется в тот момент — читай: я не нашел лучшего решения. Хотя исполняемый файл может быть ошибочно поставлен в очередь, проверка isDisposed () (которая выполняется в потоке пользовательского интерфейса) в конечном итоге не позволит выполнить код.

И есть еще один (по общему признанию, малый) шанс для проблемы с поточностью: перед тем, как (a) syncE‌xec () будет вызван, дисплей проверяется на удаление, чтобы не столкнуться с исключением из удаленного виджета. Но именно это может произойти, если дисплей будет расположен между проверкой и вызовом (a) syncE‌xec (). Хотя это можно решить для asyncE‌xec (), обернув вызов в блок try-catch, который игнорирует исключения, связанные с виджетами, такой же подход не работает для syncE‌xec (). Исключения SWTEx, генерируемые исполняемым файлом, нельзя отличить от исключений, генерируемых syncE‌xec (), при разумных усилиях.