На этом шаге мы рассмотрим особенности использования синхронизации.
В комплект поставки Delphi 7 входит пример использования класса TThread - сортировка массива случайных чисел по трем разным алгоритмам. Его можно найти в каталоге Delphi7 | Demos | Threads. В процессе сортировки промежуточные значения выводятся на экран, и пользователь может наблюдать за сортировкой массивов (рисунок 1).
Рис.1. Результат сортировки случайного массива по разным алгоритмам
Проанализируем теперь, как графика выводится на экран. Открыв файл SortThds.pas, можно найти метод DoVisualSwap, ответственный за прорисовку изображения во время выполнения приложения. Ничего необычного в самом методе нет, необычно то, как он вызывается для выполнения прорисовки. Это происходит с использованием метода Synchronize в методе VisualSwap потока:
Synchronize(DoVisualSwap);
Метод Synchronize определен в классе TThread и в качестве параметра он принимает адрес другого метода, который, в свою очередь, не должен содержать параметров и должен быть объявлен как процедура. Для понимания того, что он делает, вызовем метод DoVisualSwap без вызова Synchronize:
procedure TSortThread.VisualSwap(A, В, I, J: Integer); begin FA := A; FB := B; FI := I; FJ := J: DoVisualSwap; end;
Если запустить данное приложение, щелкнуть на кнопке Start Sorting и в процессе его выполнения водить указателем мыши по форме с графическим изображением, то периодически в графическом изображении будут наблюдаться дефекты.
Этот тест хорошо выполняется па платформах Windows 95/98/2000 и плохо на платформе Windows NT/XP, в которой редко происходит переключение между потоками. Более того, если данный тест выполнять из Delphi в режиме отладки, то периодически будет появляться исключение EIllegalOperation со следующим сообщением:
Canvas doesnot allow drawing
Объяснение этого теста заключается в том, что при работе с потоками один поток прерывает выполнение другого в произвольный момент времени, в том числе и самый неподходящий с точки зрения программиста. Если при этом оба конкурирующих потока (или хотя бы один из них) производят модификацию какой-либо общей переменной, то при программировании таких приложений необходимо применять специальные меры, которые называются синхронизацией. В данном примере происходит прорисовка графики из потоков, которые осуществляют сортировку. Кроме того, из основного потока происходит прорисовка указателя мыши в новом положении. Все потоки обращаются к общей области памяти, содержимое которой выводится на экран. Рассмотрим временную диаграмму, которая объяснит появление дефектов при отсутствии синхронизации.
На самом деле обращение к графической памяти - процесс гораздо более сложный, чем описано выше. При обращении происходит инициализация переменных и изменение состояния объекта. Если потоки переключаются в процессе инициализации, то с большой вероятностью состояние объекта является неопределенным - например, графическая память не готова к изменениям. Если в этот момент попытаться изменить содержимое памяти, то возбуждается упомянутое выше исключение EIllegalOperation.
На следующем шаге мы закончим изучение этого вопроса.