Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Начинающим";
Текущий архив: 2005.09.18;
Скачать: [xml.tar.bz2];

Вниз

Потоки   Найти похожие ветки 

 
Неуч   (2005-08-08 07:09) [0]

Помогите разобраться с потоками.
Читал литературу, но недоходит.
Приведите пожалуста пример юниты создания "потоковой" процедуры
с каким небудь понятным действием, желательно пример по (
procedure lom (a:integer; b:integer):integer;
begin
result:=a+b;
end;
), пример вызова, а также удаления, уничтожения, free этого потока.
Остальное надеюсь, разберусь сам.

Зарание спасибо.


 
begin...end ©   (2005-08-08 07:16) [1]

> Неуч   (08.08.05 7:09)

Пример нужен на API, или подойдёт стандартный класс TThread?

> result:=a+b

Вы уверены, что это следует выносить в отдельный поток?


 
Неуч   (2005-08-08 07:25) [2]

Пример нужен на API, или подойдёт стандартный класс TThread?
А можно и так и так.

> result:=a+b

Вы уверены, что это следует выносить в отдельный поток?


Это просто ради примера. А вообще мне надо вынести петли.

procedure lom (a:integer; b:integer):integer;
тут я конечно попутал это не функция


 
begin...end ©   (2005-08-08 08:08) [3]

> Неуч   (08.08.05 7:25) [2]

Вот пример с использованием TThread. Для примера поставим нашему потоку следующую задачу: каждую секунду он должен генерировать случайное число в заданном диапазоне и отображать его в заголовке формы. Как это сделать?

В Delphi есть класс TThread. Этот класс является обёрткой над системным объектом "поток". Напрямую пользоваться TThread не следует, т.к. он содержит абстрактный метод Execute. Поэтому каждый раз, когда требуется описать свой поток, нужно объявлять потомка от TThread (в приведённом ниже коде он назван TMyThread) и реализовывать в нём то, что нужно.

Решить нужно 3 подзадачи:

1. Как указать потоку, в КАКОМ диапазоне он должен будет генерировать случайное число? Другими словами, как передать потоку какой-нибудь параметр при его создании?
2. Где и как разместить код генерации случайного числа, т.е. основной код потока?
3. Как отобразить сгенерированное число в заголовке формы?

Решаем каждую из подзадач:

1. У класса TThread, как и у всех других классов, есть конструктор. Он создаёт объект класса TThread. Единственным параметром конструктора TThread.Create является параметр CreateSuspended. Если при создании потока указать CreateSuspended = True, то поток не начнёт работать сразу после создания. В этом случае, чтобы он начал работать, нужно будет вызвать метод Resume. Если же указать CreateSuspended = False, то поток запустится сразу же после вызова конструктора. Мы будем передавать False. Чтобы передать потоку при его создании ещё какой-нибудь параметр, можно переопределить его конструктор. Этот новый конструктор запоминает переданный параметр "внутри" объекта потока, а затем вызывает старый конструктор.

2. Код потока нужно разместить в методе Execute. Т.к. этот метод в классе TThread является абстрактным, его нужно перекрыть (описать с директивой override). Внутри этого метода мы и разместим нужный код -- тот код, ради которого и затевалось создание потока. Здесь следует иметь в виду следующее. Во-первых, внутри Execute НЕЛЬЗЯ непосредственно обращаться к потоконебезопасным объектам VCL (например, к форме). Во-вторых, внутри Execute НУЖНО отслеживать, следует ли потоку продолжать свою работу, или надо уже завершаться.

3. Поскольку из Execute нельзя напрямую обратиться к форме, чтобы отобразить число в её заголовке, то для этого нужно использовать метод Synchronize (об этом написано в комментарии, который Вы видите, когда создаёте поток в отдельном модуле: File -> New -> Thread Object).

Создайте пустую форму, добавьте на неё две кнопки. Код модуля должен быть таким:

unit Unit1;

interface

uses
 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
 Dialogs, StdCtrls;

type

 TMyThread = class(TThread)
   FRange: Integer;
   FValue: Integer;
   constructor Create(CreateSuspended: Boolean; Range: Integer);
   procedure UpdateForm;
   procedure Execute; override;
 end;

 TForm1 = class(TForm)
   Button1: TButton; // Кнопка создания и запуска потока
   Button2: TButton; // Кнопка завершения и уничтожения потока
   procedure Button1Click(Sender: TObject);
   procedure Button2Click(Sender: TObject);
 private
   MyThread: TMyThread;
 end;

var
 Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
 MyThread := TMyThread.Create(False, 10000);
 MyThread.FreeOnTerminate := True
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
 MyThread.Terminate
end;

{ TMyThread }

constructor TMyThread.Create(CreateSuspended: Boolean; Range: Integer);
begin
 FRange := Range;                   // Запоминаем диапазон
 inherited Create(CreateSuspended)  // Вызываем старый конструктор
end;

procedure TMyThread.UpdateForm;
begin
 Form1.Caption := IntToStr(FValue)
end;

procedure TMyThread.Execute;
begin
 Randomize;
 while not Terminated do     // Проверяем, не пора ли завершаться
 begin
   FValue := Random(FRange); // Генерируем очередное случайное число
   Synchronize(UpdateForm);  // Отображаем число в заголовке формы
   Sleep(1000)               // Ждём 1 секунду
 end
end;

end.


Итак, по нажатию Button1 создаётся объект MyThread (он сразу же начинает работать -- ежесекундно генерировать случайные числа в указанном диапазоне) и указывается, что он должен уничтожиться сам после завершения выполнения кода потока (свойство FreeOnTerminate). А по нажатию Button2 вызывается метод Terminate. Этот метод не делает ничего, кроме того, что устанавливает свойство потока Terminated в True, тем самым давая потоку понять, что он должен завершиться. Значение Terminated мы постоянно (в цикле) отслеживаем внутри метода Execute. Как только Terminated становится равным True, цикл завершается, заканчивается код потока (ведь в методе Execute после этого цикла ничего нет), и, поскольку FreeOnTerminate = True, поток сам себя уничтожает.


 
Неуч   (2005-08-08 08:27) [4]

Ага, в общих чертах понятно. Спасибо:)

Вот только несколько вопросов по примеру.

type

TMyThread = class(TThread)
  FRange: Integer;
  FValue: Integer;

  constructor Create(CreateSuspended: Boolean; Range: Integer);
 procedure UpdateForm;
  procedure Execute; override;
end;

То что жирным здесь объявления могут быть произвольными или как?
(Просто делфей щас под рукой нету, чтобы проверить)

inherited Create(CreateSuspended)  // Вызываем старый конструктор

и как понять это


 
begin...end ©   (2005-08-08 08:37) [5]

> Неуч   (08.08.05 8:27) [4]

> То что жирным здесь объявления могут быть
> произвольными или как?

Они нужны в этом примере. В других задачах они не нужны, а вместо них понадобится что-то другое. FRange -- это поле внутри объекта, в котором мы запоминаем переданный через конструктор параметр Range. FValue -- это значение очередного случайного числа, которое тоже нужно где-то запоминать, поскольку число генерируется в методе Execute, а отображается -- в методе UpdateForm (который вызывается в рамках Synchronize).

> inherited Create(CreateSuspended)  // Вызываем старый конструктор
>
> и как понять это

Так и понимать. Чтобы поток запустился, нужно вызвать конструктор класса-предка: TThread. Вот он и вызывается.


 
Неуч   (2005-08-08 09:05) [6]

Большое спасибо. Оказывается, как все просто.


 
boalse ©   (2005-08-09 09:47) [7]

А как на апях сделать?


 
Leonid Troyanovsky ©   (2005-08-09 09:53) [8]


> boalse ©   (09.08.05 09:47) [7]
> А как на апях сделать?


Синхронизацию с GUI thread? Через SendMessage, например.

--
Regards, LVT


 
boalse ©   (2005-08-10 08:11) [9]

Я имел в виду, как организовать работу с потоками на API? Желательно пример, буду весьма признателен.


 
Неуч   (2005-08-10 09:20) [10]

Мне тоже интересно


 
begin...end ©   (2005-08-10 10:54) [11]

> boalse ©   (10.08.05 08:11) [9]
> Неуч   (10.08.05 09:20) [10]

Можно использовать API-функцию CreateThread или её Delphi-обёртку BeginThread. Последнее предпочтительнее, поскольку BeginThread информирует менеджер памяти о том, что в приложении есть несколько потоков, в результате чего он начинает работать с кучей, используя критические секции. Поэтому пример будет именно с BeginThread.

Будем решать ту же задачу, что и выше -- каждую секунду в отдельном потоке генерировать случайное число и отображать его в заголовке формы.

Одним из параметров BeginThread является указатель на функцию типа TThreadFunc. Это и есть поточная функция -- её код будет выполняться в контексте дополнительного потока. У неё, согласно описанию типа TThreadFunc, должен быть только один параметр типа Pointer. Таким образом, мы можем передать ей указатель на какие-то данные (по которому она будет находить эти данные в памяти) или непосредственно значение любой переменной, занимающей в памяти не более 4 байт (размер типа Pointer). В примере ниже будем использовать первый способ. Эта функция (опять же, согласно её описанию) возвращает целое число (Integer). Пусть в нашем случае она будет возвращать количество сгенерированных чисел.

Как запустить поток, мы уже примерно знаем -- с помощью BeginThread. Теперь нужно выяснить, как завершить поток, или, точнее, как сообщить ему, что пора завершаться. Открываем MSDN и читаем статью "Terminating a Thread" ("Завершение потока"):

The following steps provide a better solution:

Create an event object using the CreateEvent function.
Create the threads.
Each thread monitors the event state by calling the WaitForSingleObject function. Use a wait time-out interval of zero.
Each thread terminates its own execution when the event is set to the signaled state (WaitForSingleObject returns WAIT_OBJECT_0).


Теперь -- краткий перевод. Лучшим решением авторы MSDN считают следующее: перед созданием потока нужно создать объект "событие" (CreateEvent), затем создать поток и внутри его поточной функции время от времени проверять состояние объекта "событие". "Событие" -- это, говоря очень упрощённо, объект, подобный переменной типа Boolean. Он может находиться в двух состояниях -- событие может наступить или нет, третьего не дано. Так вот -- когда поток при очередной проверке (проверять состояние события можно с помощью функции WaitForSingleObject) обнаружит, что событие наступило, он завершается. Причём сам, без посторонней помощи. А мы, управляя состоянием объекта "событие" из главного потока, можем управлять потоком. Когда нужно остановить поток, мы меняем состояние события, и, как только дополнительный поток это заметит, он сам завершится.

Что нужно передать поточной функции в нашем случае? Очевидно, диапазон, в котором должен работать генератор. Но это ещё не всё. Во-первых, только что мы выяснили, что поток должен отслеживать состояние объекта "событие" -- а значит, ему надо передать хэндл этого объекта. Во-вторых, из дополнительного потока по-прежнему нельзя напрямую обращаться к потоконебезопасным объектам VCL, в том числе к формам. И на API уже не будет удобного способа обратиться к форме с помощью Synchronize. Как же быть? Можно из функции посылать форме сообщения -- как только сгенерировали число, сразу же посылаем форме сообщение и передаём это число в параметре сообщения. Форма, получая сообщение, будет обрабатывать его, брать из параметра число и отображать его в заголовке. Но поточная функция должна знать, куда отправлять сообщения. Поэтому ей нужно передавать ещё и хэндл окна формы. Итак, передавать нужно три параметра -- диапазон генератора, хэндл события и хэндл окна.

Остался нерешённым один вопрос -- как получить значение, возвращаемое поточной функцией? Когда мы будем завершать поток (в конце поточной функции) с помощью функции EndThread, мы передадим её параметр -- результат поточной функции. В основном потоке "код выхода" завершившегося потока (а это и есть параметр, переданный в EndThread) можно будет получить функцией GetExitCodeThread.

Продолжение -- в следующем посте.


 
begin...end ©   (2005-08-10 10:55) [12]

Теперь можно приступать к реализации. Опишем в модуле формы тип записи, которая предназначена для хранения  параметров, и тип указателя на неё:

type
 
 PParam = ^TParam;
 TParam = record
   hEvent: Cardinal;  // Хэндл события
   hForm:  Cardinal;  // Хэндл окна формы
   Range:  Cardinal;  // Диапазон генератора
 end


Нужно определиться с тем, какое сообщение мы будем посылать форме. Определим его как WM_USER + 100:

const
 WM_MYMESSAGE = WM_USER + 100


(хотя лучше для этих целей использовать функцию RegisterWindowMessage).

Теперь можно написать код поточной функции (мы будем ей передавать указатель на запись типа TParam):

function Func(Parameter: Pointer): Integer;
var
 Param: TParam;
begin
 // Получаем переданные параметры
 Param := PParam(Parameter)^;
 Result := 0;
 Randomize;
 // Пока событие не наступило...
 while WaitForSingleObject(Param.hEvent, 0) <> WAIT_OBJECT_0 do
 begin
   // ...генерируем число и посылаем его форме в параметре wParam сообщения
   PostMessage(Param.hForm, WM_MYMESSAGE, Random(Param.Range), 0);
   // Увеличиваем результат
   Inc(Result);
   // Ждём 1 секунду
   Sleep(1000)
 end;
 // Так как событие наступило, то выходим
 EndThread(Result)
end


Пишем обработчик сообщения WM_MYMESSAGE в форме:

type
 TForm1 = class(TForm)
 ...
 private
   procedure WMMyMessage(var Message: TMessage); message WM_MYMESSAGE;
 ...
 end;

procedure TForm1.WMMyMessage(var Message: TMessage);
begin
 // Отображаем число, присланное в параметре wParam, в заголовке
 Caption := IntToStr(Message.wParam)
end


Теперь осталось только написать код создания потока:

var
 // Всё это -- глобальные переменные!
 P:  TParam;    // Параметры
 H:  Cardinal;  // Хэндл потока
 Id: Cardinal;  // Идентификатор потока
begin
 P.Range := 10000;
 P.hForm := Handle;
 P.hEvent := CreateEvent(nil, False, False, "MyUniqueEvent");
 Win32Check(P.hEvent <> 0);
 H := BeginThread(nil, 0, @Func, @P, 0, Id);
 Win32Check(H <> 0)
end


и код его завершения:


var
 Code: Cardinal;
begin
 // "Взводим" событие
 SetEvent(P.hEvent);
 // Ждём, пока поток завершится
 WaitForSingleObject(H, INFINITE);
 // Смотрим на результат поточной функции
 GetExitCodeThread(H, Code);
 ShowMessage(IntToStr(Code));
 // Закрываем хэндлы
 CloseHandle(P.hEvent);
 CloseHandle(H)
end


Окончание -- в следующем посте.


 
begin...end ©   (2005-08-10 10:55) [13]

В заключение -- код модуля и файл формы.

Модуль (файл .pas):

unit Unit1;

interface

uses
 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
 Dialogs, StdCtrls;

const
 WM_MYMESSAGE = WM_USER + 100;

type

 PParam = ^TParam;
 TParam = record
   hEvent: Cardinal;
   hForm:  Cardinal;
   Range:  Cardinal;
 end;

 TForm1 = class(TForm)
   Button1: TButton;
   Button2: TButton;
   procedure Button1Click(Sender: TObject);
   procedure Button2Click(Sender: TObject);
 private
   procedure WMMyMessage(var Message: TMessage); message WM_MYMESSAGE;
 public
 end;

var
 Form1: TForm1;

 P:  TParam;
 H:  Cardinal;
 Id: Cardinal;

implementation

function Func(Parameter: Pointer): Integer;
var
 Param: TParam;
begin
 Param := PParam(Parameter)^;
 Result := 0;
 Randomize;
 while WaitForSingleObject(Param.hEvent, 0) <> WAIT_OBJECT_0 do
 begin
   PostMessage(Param.hForm, WM_MYMESSAGE, Random(Param.Range), 0);
   Inc(Result);
   Sleep(1000)
 end;
 EndThread(Result)
end;

{$R *.dfm}

procedure TForm1.WMMyMessage(var Message: TMessage);
begin
 Caption := IntToStr(Message.wParam)
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
 P.Range := 10000;
 P.hForm := Handle;
 P.hEvent := CreateEvent(nil, False, False, "MyUniqueEvent");
 Win32Check(P.hEvent <> 0);
 H := BeginThread(nil, 0, @Func, @P, 0, Id);
 Win32Check(H <> 0)
end;

procedure TForm1.Button2Click(Sender: TObject);
var
 Code: Cardinal;
begin
 SetEvent(P.hEvent);
 WaitForSingleObject(H, INFINITE);
 GetExitCodeThread(H, Code);
 ShowMessage(IntToStr(Code));
 CloseHandle(P.hEvent);
 CloseHandle(H)
end;

end.


Форма (файл .dfm):

object Form1: TForm1
 Left = 192
 Top = 114
 Width = 422
 Height = 213
 Caption = "Form1"
 Color = clBtnFace
 Font.Charset = DEFAULT_CHARSET
 Font.Color = clWindowText
 Font.Height = -11
 Font.Name = "MS Sans Serif"
 Font.Style = []
 OldCreateOrder = False
 PixelsPerInch = 96
 TextHeight = 13
 object Button1: TButton
   Left = 96
   Top = 48
   Width = 89
   Height = 49
   Caption = #1047#1072#1087#1091#1089#1090#1080#1090#1100
   TabOrder = 0
   OnClick = Button1Click
 end
 object Button2: TButton
   Left = 232
   Top = 48
   Width = 81
   Height = 49
   Caption = #1054#1089#1090#1072#1085#1086#1074#1080#1090#1100
   TabOrder = 1
   OnClick = Button2Click
 end
end


 
Неуч   (2005-08-10 12:56) [14]

begin...end ©   (10.08.05 10:54) [11]
Спасибо!
ps. Глядишь и нормальный хелп по потокам соберется


 
Leonid Troyanovsky ©   (2005-08-10 15:20) [15]


> boalse ©   (10.08.05 08:11) [9]
> Я имел в виду, как организовать работу с потоками на API?
> Желательно пример, буду весьма признателен.


Простых (обучающих) примеров с использованием в дельфи
CreateThread я не знаю.
Сами же вызовы выглядят примерно так:

http://groups-beta.google.com/group/fido7.ru.delphi/msg/5c93490dd70154aa

Хотя, в данном случае, использование CT неоправданно,
если исходить из следующих правил:

1. Не использовать потоки без нужды.
2. В GUI приложениях использовать TThread.
3. В консольных - BeginThread.

Т.о., на долю CT остаются весьма экзотические случаи вроде
создания потоков в dll (при условии, что эти библиотеки должны
использоваться не только дельфийскими приложениями).

--
Regards, LVT.


 
Leonid Troyanovsky ©   (2005-08-10 15:23) [16]


> Неуч   (10.08.05 12:56) [14]

> ps. Глядишь и нормальный хелп по потокам соберется


Чарльз Калверт. "Дельфи Х. Энциклопедия пользователя"
Джефри Рихтер. Windows для профессионалов.

Ну, и

Tutorial on thread programming in Delphi:

http://www.pergolesi.demon.co.uk/prog/threads/ToC.html
http://www.sklobovsky.com/community/threadmare/threadmare.htm

Handling exceptions in a thread:
http://community.borland.com/article/0,1410,10452,00.html

by Tolik Tentser:

http://www.compress.ru/Article.asp?id=2164

--
Regards, LVT.


 
boalse ©   (2005-08-11 04:16) [17]

Всем онромное спасибо, особенно begin...end.



Страницы: 1 вся ветка

Форум: "Начинающим";
Текущий архив: 2005.09.18;
Скачать: [xml.tar.bz2];

Наверх




Память: 0.53 MB
Время: 0.01 c
1-1124960267
kyn66
2005-08-25 12:57
2005.09.18
Как привязаться к координатам картинки ?


14-1124434354
Jeer
2005-08-19 10:52
2005.09.18
О "партнерстве" США с Россией


3-1123158611
Вольный Стрелок
2005-08-04 16:30
2005.09.18
Как определить список пользовательских (несистемных) индексов


3-1123046809
-=snoop=-
2005-08-03 09:26
2005.09.18
не отрабатывается селект к базе((


3-1123228105
Ищущий
2005-08-05 11:48
2005.09.18
выполнение анимации при работе с базой





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский