Руководство программиста в Photon (1037671), страница 44
Текст из файла (страница 44)
PtWorkProcId_t *WorkProc_id );
передавая ей тот же контекст приложения и указатель, возвращённый функцией PtApppAddWorkProc().
Реальный пример использования рабочей процедуры слишком велик, чтобы размещать его здесь, поэтому вот простой итеративный пример. Рабочая процедура отсчитывает большое число, периодически обновляя надпись, чтобы отражать ход её исполнения.
#include <Pt.h>
typedef struct workDialog {
PtWidget_t *widget;
PtWidget_t *label;
PtWidget_t *ok_button;
} WorkDialog_t;
typedef struct countdownClosure {
WorkDialog_t *dialog;
int value;
int maxvalue;
int done;
PtWorkProcId_t *work_id;
} CountdownClosure_t;
WorkDialog_t *create_working_dialog(PtWidget_t *parent) {
PhDim_t dim;
PtArg_t args[3];
int nargs;
PtWidget_t *window, *group;
WorkDialog_t *dialog = (WorkDialog_t *) malloc(sizeof(WorkDialog_t));
if (dialog) {
dialog->widget = window = PtCreateWidget(PtWindow, parent, 0, NULL);
nargs = 0;
PtSetArg(&args[nargs], Pt_ARG_GROUP_ORIENTATION, Pt_GROUP_VERTICAL, 0);
nargs++;
PtSetArg(&args[nargs], Pt_ARG_GROUP_VERT_ALIGN, Pt_GROUP_VERT_CENTER, 0);
nargs++;
group = PtCreateWidget(PtGroup, window, nargs, args);
nargs = 0;
dim.w = 200;
dim.h = 100;
PtSetArg(&args[nargs], Pt_ARG_DIM, &dim, 0); nargs++;
PtSetArg(&args[nargs], Pt_ARG_TEXT_STRING, "Counter: ", 0); nargs++;
dialog->label = PtCreateWidget(PtLabel, group, nargs, args);
PtCreateWidget(PtSeparator, group, 0, NULL);
nargs = 0;
PtSetArg(&args[nargs], Pt_ARG_TEXT_STRING, "Stop", 0); nargs++;
dialog->ok_button = PtCreateWidget(PtButton, group, 1, args);
} // if(dialog)
return dialog;
} // create_working_dialog()
int done(PtWidget_t *w, void *client, PtCallbackInfo_t *call) {
CountdownClosure_t *closure = (CountdownClosure_t *)client;
call = call;
if (!closure->done) { PtAppRemoveWorkProc(NULL, closure->work_id); }
PtDestroyWidget(closure->dialog->widget);
free(closure->dialog);
free(closure);
return (Pt_CONTINUE);
} // done()
int count_cb(void *data) {
CountdownClosure_t *closure = (CountdownClosure_t *)data;
char buf[64];
int finished = 0;
if ( closure->value++ = = 0 || closure->value % 1000 = = 0 ) {
sprintf(buf, "Counter: %d", closure->value);
PtSetResource( closure->dialog->label, Pt_ARG_TEXT_STRING, buf, 0);
}
if ( closure->value = = closure->maxvalue ) {
closure->done = finished = 1;
PtSetResource( closure->dialog->ok_button, Pt_ARG_TEXT_STRING, "Done", 0);
}
return finished ? Pt_END : Pt_CONTINUE;
} // count_cb()
int push_button_cb(PtWidget_t *w, void *client, PtCallbackInfo_t *call) {
PtWidget_t *parent = (PtWidget_t *)client;
WorkDialog_t *dialog;
w = w; call = call;
dialog = create_working_dialog(parent);
if (dialog) {
CountdownClosure_t *closure = (CountdownClosure_t *) malloc(sizeof(CountdownClosure_t));
if (closure) {
PtWorkProcId_t *id;
closure->dialog = dialog;
closure->value = 0;
closure->maxvalue = 200000;
closure->done = 0;
closure->work_id = id =
PtAppAddWorkProc(NULL, count_cb, closure);
PtAddCallback(dialog->ok_button, Pt_CB_ACTIVATE, done, closure);
PtRealizeWidget(dialog->widget);
} // if (closure)
} // if (dialog)
return (Pt_CONTINUE);
}
int main(int argc, char *argv[]) {
PhDim_t dim;
PtArg_t args[3];
int n;
PtWidget_t *window;
PtCallback_t callbacks[] = {{push_button_cb, NULL}};
char Helvetica14b[MAX_FONT_TAG];
if (PtInit(NULL) = = -1) PtExit(EXIT_FAILURE);
dim.w = 200;
dim.h = 100;
PtSetArg(&args[0], Pt_ARG_DIM, &dim, 0);
if ((window = PtCreateWidget(PtWindow, Pt_NO_PARENT, 1, args)) = = NULL)
PtExit(EXIT_FAILURE);
callbacks[0].data = window;
n = 0;
PtSetArg(&args[n++], Pt_ARG_TEXT_STRING, "Обратный отсчёт...", 0);
/* Используется шрифт Helvetica 14-пунктовый жирный, если тот доступен */
if (PfGenerateFontName("Helvetica", PF_STYLE_BOLD, 14, Helvetica14b) = = NULL) {
perror("невозможно сгенерировать имя шрифта");
}
else PtSetArg(&args[n++], Pt_ARG_TEXT_FONT, Helvetica14b, 0);
PtSetArg(&args[n++], Pt_CB_ACTIVATE, callbacks, sizeof(callbacks)/sizeof(PtCallback_t));
PtCreateWidget(PtButton, window, n, args);
PtRealizeWidget(window);
PtMainLoop();
return (EXIT_SUCCESS);
}
Когда нажимается кнопка, прикреплённая к ней ответная реакция создаёт рабочий диалог и добавляет рабочую процедуру, передавая в качестве параметра указатель closure на структуру, содержащую всю информацию, необходимую для выполнения отсчёта и очистки, когда отсчёт будет выполнен.
Параметр closure содержит указатель на диалог, текущее значение счётчика и значение, до которого ведётся отсчёт. Когда это значение будет достигнуто, рабочая процедура изменит надпись на кнопке диалога и подсоединит ответную реакцию, которая снесёт напрочь диалог в целом, когда кнопка будет нажата. Будучи так вынужденной, рабочая процедура вернёт Pt_END, чтобы быть удалённой.
Функция done() вызывается, если пользователь останавливает рабочую процедуру или если она завершает свою работу. Эта функция удаляет диалог, связанный с рабочей процедурой, и удаляет рабочую процедуру, если та была остановлена пользователем (т.е. она не выполнилась вплоть до завершения).
Если Вы запустите этот пример на исполнение, Вы можете обнаружить ещё одну из возможностей рабочих процедур – они вытесняют одна другую. Когда Вы добавляете новую рабочую процедуру, она вытесняет все остальные. Новая рабочая процедура отработает только один раз, пока не завершится или не будет удалена. После этого возобновится исполнение рабочей процедуры, исполнявшейся до этого. Это проиллюстрировано в вышеприведенном примере – в случае, когда пользователь нажимает кнопку "Count Down..." до того, как отсчёт завершится. Создаётся новый диалог отсчёта, и этот отсчёт будет исполняться вместо первого, пока не выполнится.
Уровень модульности этой вытесняемости – на уровне вызова функции. Когда функция ответной реакции для рабочей процедуры вернёт управление, эта рабочая процедура может быть вытеснена другой рабочей процедурой.
Потоки
Приложения Photon'а являются приложениями, управляемыми событиями и базирующимися на ответных реакциях; каждый раз, когда приходит событие, вызывается соответствующая ответная реакция и обрабатывает это событие, и затем управление возвращается в петлю событий на ожидание следующего события. В связи с такой структурой большинство приложений Photon'а являются однопоточными. Библиотека Photon'а позволяет Вам использовать потоки, но путём минимизации накладных расходов для однопоточных приложений. Библиотека Photon'а является скорее "дружественной по отношению к потокам", чем полностью потоко-безопасной, как функции printf() и malloc(), являющиеся потоко-безопасными.
Не отменяйте поток, который может исполнять функцию библиотеки Photon'а или ответную реакцию (поскольку библиотека, возможно, должна выполнить какую-нибудь очистку, когда ответная реакция вернёт управление).
Этот раздел включает:
-
Запирание библиотеки Photon'а
-
Несколько потоков, обрабатывающих события
-
Потоки реального времени
-
Не- Photon'овские и Photon'овские потоки
-
Модальные операции и потоки
-
Завершение многопоточной программы
-
Потоки и рабочие процедуры
Запирание библиотеки Photon'а
Вы можете использовать несколько потоков, устраивая Вашу программу таким образом, чтобы только поток, вызвавший функцию PtInit(), вызывал функции Photon'а, но Вы можете найти подобный подход слишком ограниченным. Библиотека Photon'а является по большей части однопотоковой, но имеет механизм, позволяющий безопасно использовать множество потоков. Этим механизмом является библиотечный замок, предоставляемый функциями PtEnter() и PtLeave(). Этот замок похож на большой взаимоисключающий семафор (мутекс), защищающий библиотеку Photon'а; только один поток может владеть замком в данный момент времени, и только этому потоку позволено выполнять вызовы библиотечных функций Photon'а. Любой другой поток, желающий выполнить вызов функции Photon'а, должен прежде выполнить вызов функции PtEnter(), которая блокирует его до тех пор, пока замок не станет доступным. Когда потоку больше не нужен замок, он вызывает функцию PtLeave(), чтобы позволить использовать библиотеку Photon'а другим потокам.
Чтобы написать Ваши не-Photon'овские потоки:
-
Поставьте вызовы функций PtEnter() и PtLeave() вокруг каких-либо вызовов Photon'овских функций в этих потоках.
-
Сгруппируйте вместе весь Photon'овский код, который сможете, и заключите его в одну пару вход/выход, поскольку это минимизирует количество потенциально сблокированных вызовов PtEnter() в Вашем коде.
-
Попытайтесь убрать весь не-Photon'овский код, который может забирать время для своего исполнения, из Вашей секции вход/выход – в противном случае он может безо всяких на то оснований препятствовать другим потокам в выполнении их работы.
Не вызывайте функцию PtLeave(), если Ваш поток не вызывал PtEnter(), или Ваше приложение может неправильно себя вести либо приведёт к аварии. Помните, что если Вы находитесь в функции ответной реакции, что-то должно вызвать PtEnter(), чтобы позволить Вам попасть туда.
Функция PtLeave() не передаёт автоматически библиотечный замок другому потоку, блокированному внутри PtEnter(); другой поток становится разблокированным, но затем он должен конкурировать со всеми другими потоками, как если бы он просто вызвал PtEnter(). Вы должны использовать функции PtEnter() и PtLeave() вместо своего собственного мутекса, потому что когда функция PtProcessEvent() (которую вызывает функция PtMainLoop() ) ожидает события, она отпирает библиотеку. Как только PtProcessEvent() получает некое событие, которое она может обработать, она вновь запирает библиотеку. Таким образом, Ваши не-Photon'овские потоки могут свободно получить доступ к функциям Photon'а, когда у Вас нет никаких событий для обработки.
Если Вы используете свой собственный мутекс, о котором PtProcessEvent() не знает, он отопрётся только тогда, когда его отопрёт Ваш код. Это означает, что только то время, когда Ваши не-Photon'овские потоки могут запереть мутекс, является временем, когда Ваше приложение обрабатывает событие, которое вызвало одну из Ваших ответных реакций. Не-Photon'овские потоки не могут запирать мутекс, когда приложение находится в состоянии ожидания.
Несколько потоков, обрабатывающих события
Если вам требуется в Вашем приложении долго исполняемая ответная реакция, Вы можете сделать так, чтобы Ваша ответная реакция вызывала функцию PtBkgdHandlerProcess(), как было описано выше в этой главе. Вы можете также породить новый поток, для выполнения этой работы, вместо того чтобы делать её в ответной реакции.
Другим выбором является создание более одного Photon'овского потока, обрабатывающего события Photon'а в Вашем приложении. Вот так:
-
Породить один или более дополнительных потоков, которые вызывают функцию PtEnter() вслед за PtMainLoop(). Если один из Ваших Photon'овских потоков получает некое событие, которое вызывает Вашу долго выполняющуюся ответную реакцию, остальные потоки могут принимать на себя обработку событий Photon'а.
-
Вызвать функцию PtLeave() из ответной реакции, чтобы дать другим потокам доступ к библиотеку Photon'а.
-
Не забыть вызвать функцию PtEnter() перед выходом из ответной реакции; код, вызвавший Вашу ответную реакцию, расчитывает на собственный замок Photon'а, когда ответная реакция вернула управление.
Отпирание библиотеки позволяет другим потокам модифицировать Ваши виджеты и глобальные переменные, пока Вы не видите, так что будьте внимательны.
Если Ваша ответная реакция позволяет другим потокам обрабатывать событие, пока она выполняет свою долго выполняющуюся операцию, имеется вероятность того, что пользователь, владеющий мышью, может нажать определённую кнопку вновь, вызывая вашу ответную реакцию до того, как та завершила выполнение своего первого вызова. Вы должны обеспечить, чтобы Ваше приложение либо корректно обрабатывало эту ситуацию, либо принять меры, чтобы такого не случалось. Вот несколько способов, как это осуществить:
-
Блокировать Вашу кнопку перед тем, как ответная реакция вызовет PtLeave(), и разблокировать её после вызова функции PtEnter()
или
-
Использовать флаг, указывающий повторно вызванной ответной реакции, что она уже запущена
или
-
Использовать счётчик, если вы хотите подсчитать, а не просто игнорировать все дополнительные нажатия кнопки
или
-
Использовать Ваш собственный мутекс или другой механизм синхронизации, чтобы гарантировать, что Вы не наступаете на свои собственные пятки (но остерегайтесь возможных тупиков).
Потоки реального времени
Не создавайте вызовов Photon'овских функций в потоках, которые должны иметь детерминированное поведение, удовлетворяющее требованиям реального времени. Сложно прогнозировать, как долго будет продолжаться блокирование на PtEnter(); это может забрать время у потока, который владеет замком для завершения обработки текущего события или вызова PtLeave(), особенно если это касается отсылки сообщения другим процессам (таким как менеджер окон).
Лучше иметь "рабочий поток", удовлетворяющий требованиям к Вашим потокам реального времени, и исполнять этот поток в его собственной секции входа/выхода. Переменная состояния – и, возможно, очередь запросов – является хорошим способом отсылки этих запросов между потоками.
Если Вы используете рабочие потоки, и Вам надо использовать переменную состояния, вызовите вместо функции pthread_cond_wait() функцию PtCondWait() и отделите мутекс. Функция PtCondWait() использует замок библиотеки Photon'а как мутекс и исполняет безусловный вызов PtLeave(), когда Вы блокируетесь, и PtEnter() – когда разблокируетесь.