(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Выполнение кода в потоке без выделения его в процедуру

Источник: delphikingdom
Александр Алексеев

Автор: © Александр Алексеев

Вашему вниманию (читай: для использования и тестирования) предлагается модуль TasksEx.pas, который предлагает всего две функции:

type
  TContext = type Integer;

function  EnterWorkerThread: TContext;
procedure LeaveWorkerThread(Context: TContext);

Код, помещённый между вызовами EnterWorkerThread и LeaveWorkerThread , будет выполняться как если бы он был помещён в метод TThread.Execute .

Для начала выполнения кода в другом потоке просто вставьте в код вызов EnterWorkerThread . Для обратного переключения нужно использовать LeaveWorkerThread . Эти вызовы могут быть вложенными, но _обязаны_ быть парными. Также они должны вызываться из одной и той же процедуры/функции/метода.

В процедуру LeaveWorkerThread нужно передать результат выполнения функции EnterWorkerThread . Это значение позволяет отличать несколько вызовов EnterWorkerThread друг от друга (поскольку функцию EnterWorkerThread можно вызывать многократно). Вызывающая сторона должна трактовать это значение как простое число и не должна пытаться как-либо интерпретировать его или делать предположение о его содержимом. Всё что она может сделать с ним - передать его в парный вызов LeaveWorkerThread и забыть про него после вызова.

Рекомендованная конструкция:

var
  Context: TContext;
begin

  // Этот код выполняется в главном потоке (например, Button1Click)

  Context := EnterWorkerThread;
  try

    { Этот код выполняется во вторичном потоке }
    { Хотя это код Button1Click, но он выполняется, как если бы он был }
    { помещён в TThread.Execute. }

  finally
    LeaveWorkerThread(Context);
    // После вызова LeaveWorkerThread переменная Context недействительна.
  end;

  // Этот код выполняется в главном потоке

end;

Весь код между EnterWorkerThread и LeaveWorkerThread выполняется во вторичном потоке. Вторичный поток выбирается случайным образом из пула свободных рабочих потоков (используется модуль AsyncCalls , см. ниже). Если таковых нет, то выполнение кода откладывается (добавляется в очередь для исполнения), пока не освободится один из рабочих потоков. Число потоков в пуле контролируется Get/SetMaxAsyncCallThreads . По-умолчанию их не меньше двух.

Во время выполнения кода во вторичном потоке или во время ожидания освобождения рабочего потока главный поток находится в цикле вызовов Application.HandleMessage . Во время этого цикла может быть вызван код, вызывающий EnterWorkerThread/LeaveWorkerThread . Поэтому в любой момент времени одновременно может выполняться несколько блоков кода между EnterWorkerThread и LeaveWorkerThread , в том числе и несколько вызовов одного и того же кода. Число одновременно выполняющихся блоков ограничено числом потоков в пуле потоков. В таких случаях выполнение кода после первого LeaveWorkerThread продолжится только после того, как все вложенные вызовы будут завершены.

Например (// - главный поток, {} - вторичные потоки ):

// вызван обработчик события Button1Click

EnterWorkerThread #1

// главный поток крутится в цикле с Application.HandleMessage
{ вторичный поток обрабатывает блок  1 }

// во время Application.HandleMessage в главном потоке происходит вызов
// EnterWorkerThread при обработке какого-либо события (например,
// Button2Click или снова Button1Click):

  EnterWorkerThread #2
  // главный поток крутится во вложенном цикле с Application.HandleMessage
  { два вторичных потока выполняют блоки  1 и  2 }
  { блок  2 = блоку  1, если повторно был вызван обработчик Button1Click }

  { блок  1 завершил выполнение, }
  { но код после LeaveWorkerThread  1 не может начать выполнение, }
  { т.к. код главного потока крутится во вложенном цикле с }
  { Application.HandleMessage }
  { поэтому идёт ожидание завершения блока  2 }

  { блок  2 завершил выполнение }

  LeaveWorkerThread #2

  // выполняется код после LeaveWorkerThread  2

  // выход из обработчика событий, Application.HandleMessage возвращает
  // управление

// остановка вложенного цикла Application.HandleMessage, т.к. блок  1
// завершил выполнение

// выполняется код после LeaveWorkerThread  1

Если блок 2 закончит работу раньше, чем блок 1, то ожидания не происходит.

Например:

// вызван обработчик события Button1Click

EnterWorkerThread #1

// главный поток крутится в цикле с Application.HandleMessage
{ вторичный поток обрабатывает блок  1 }

// во время Application.HandleMessage в главном потоке происходит вызов
// EnterWorkerThread при обработке какого-либо события:

  EnterWorkerThread #2
  // главный поток крутится во вложенном цикле с Application.HandleMessage
  { два вторичных потока выполняют блоки  1 и  2 }

  { блок  2 завершил выполнение }
  { вторичный поток обрабатывает блок  1 }

  LeaveWorkerThread #2

  // выполняется код после LeaveWorkerThread  2
  { вторичный поток обрабатывает блок  1 }

  // выход из обработчика событий, Application.HandleMessage возвращает
  // управление

// главный поток крутится в цикле с Application.HandleMessage
{ вторичный поток обрабатывает блок  1 }

{ блок  1 завершил выполнение }

LeaveWorkerThread #1

// выполняется код после LeaveWorkerThread  1

Ситуация аналогична тому, как в однопоточном приложении во время вызова Application.ProcessMessages вызывается длительный обработчик события, и Application.ProcessMessages не возвращает управления, пока обработчик не закончит работу. Эту ситуацию не следует путать с вложенным вызовом EnterWorkerThread :

...

Context := EnterWorkerThread;
try
  ...
  P;
  ...
finally
  LeaveWorkerThread(Context);
end;

...

procedure P;
var
  Context: TContext;
begin
  Context := EnterWorkerThread; // ничего не делает,
                                // т.к. мы уже во вторичном потоке
  try
    ...
  finally
    LeaveWorkerThread(Context); // ничего не делает, т.к. EnterWorkerThread
                                // ничего не делал
  end;
end;

При выходе из приложения выход откладывается, пока не будут завершены все выполняющиеся блоки вызовов.

В коде между EnterWorkerThread и LeaveWorkerThread можно использовать все локальные и глобальные переменные текущей процедуры/функции и её параметры, а также локальные переменные и парметры процедуры/функции, в которую вложена текущая процедура/функция.

Исключения, возникшие в этом коде останавливают его выполнение и перевозбуждаются уже в главном потоке. При возникновении исключения LeaveWorkerThread ничего не делает, позволяя завершиться раскрутке исключения.

Поэтому:

// Главный поток
try
  // Главный поток
  C := EnterWorkerThread;
  { Вторичный поток }
  try
    { Вторичный поток }
  finally
    { Вторичный поток }
    LeaveWorkerThread(C);
    // Если исключение не возникло, то - главый поток
    { Если исключение возникло, то - вторичный поток }
  end;
  // Главный поток (пропускается, если возникло исключение)
finally
  // Главный поток
end;
// Главный поток (пропускается, если возникло исключение)

По этой причине не рекомендуется вставлять код в Finally -блок для LeaveWorkerThread .

Интегированный отладчик Delphi не способен следить за выполнением смены потоков. Если вы отлаживаетесь и хотите сделать Step Over для Enter/LeaveWorkerThread , то вы должны поставить breakpoint сразу после вызова этих функций.

При повторном вхождении в EnterWorkerThread рабочий поток не обязан быть тем же самым, что и при первом вызове. Например:

var
  Context: TContext;
begin

  // Этот код выполняется в главном потоке

  Context := EnterWorkerThread;
  try

    { Этот код выполняется во вторичном потоке  1 }

  finally
    LeaveWorkerThread(Context);
  end;

  // Этот код выполняется в главном потоке

  Context := EnterWorkerThread;
  try

    { Этот код выполняется во вторичном потоке  2, который может быть }
    { тем же самым вторичным потоком  1, а может быть и абсолютно другим. }

  finally
    LeaveWorkerThread(Context);
  end;

  // Этот код выполняется в главном потоке

end;

Для временного переключения в главный поток используйте функции из AsyncCalls EnterMainThread/LeaveMainThread . Для них справедливы все те же замечания, что и для EnterWorkerThread/LeaveWorkerThread . Например:

var
  Context: TContext;
begin

  // Этот код выполняется в главном потоке

  Context := EnterWorkerThread;
  try

    { Этот код выполняется во вторичном потоке  1 }

    EnterMainThread;
    try

      // Этот код выполняется в главном потоке

    finally
      LeaveMainThread;
    end;

    { Этот код выполняется во вторичном потоке  1 }

  finally
    LeaveWorkerThread(Context);
  end;

  // Этот код выполняется в главном потоке

end;

Вызов EnterMainThread/LeaveMainThread подобен вызову Synchronize . Поскольку одновременно в главном потоке может выполняться лишь один код, то EnterMainThread блокирует (вызовом EnterCriticalSection ) выполнение потока, если уже есть поток, вызвавший EnterMainThread и не вызвавший ещё LeaveMainThread . Поэтому EnterMainThread/LeaveMainThread не передают контекста, т.к. он сохраняется в глобальной переменной, поскольку в любой момент времени такой контекст может быть только один. Также, во время вызова EnterMainThread/LeaveMainThread рабочий поток ожидает завершения работы блока и не возвращается в пул свободных рабочих потоков.

Модуль TaskEx.pas в текущей версии пока не обеспечивает работу с run-time пакетами. Эта возможность в процессе разработки.

Скачать пример TasksEx.zip

Для работы модуля требуется модуль AsyncCalls (версии 2.0 или выше) от Andreas Hausladen , который можно взять тут: http://andy.jgknet.de/async/

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 16.06.2008 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Business Studio 4.2 Enterprise. Конкурентная лицензия + Business Studio Portal 4.2. Пользовательская именная лицензия.
Quest Software. Toad for DBA Suite for Oracle
Quest Software. TOAD Xpert Edition
ABBYY FineReader 14 Standard Full
VMware Workstation Pro 12 for Linux and Windows, ESD
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Новости ITShop.ru - ПО, книги, документация, курсы обучения
Программирование на Microsoft Access
CASE-технологии
СУБД Oracle "с нуля"
Компьютерные книги. Рецензии и отзывы
Программирование на Visual Basic/Visual Studio и ASP/ASP.NET
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100