Asynchronous Calls/ru
│
English (en) │
français (fr) │
日本語 (ja) │
русский (ru) │
Постановка задачи
При обработке какого-либо события вам иногда нужно что-то сделать, но вы не можете сделать это сразу. Например, вам нужно освободить объект, но на него ссылаются или будут ссылаться позже где-то в родителе (или в его родителе и т.д.). Или вам нужно обновить элементы графического интерфейса, к которым обращаются несколько потоков, безопасным способом.
Решение
Вызовите Application.QueueAsyncCall:
TDataEvent = procedure (Data: PtrInt) of object;
procedure QueueAsyncCall(AMethod: TDataEvent; Data: PtrInt);
Это «поставит» данный метод с заданным параметром для выполнения в очередь в главном цикле событий, когда все остальные события будут обработаны. В приведенном выше примере ссылка на объект, который вы хотели освободить, исчезла, поскольку тогдашний родительский объект завершил выполнение, и объект, который вы хотели освободить, можно безопасно освободить.
Обратите внимание, что это более обобщенная версия ReleaseComponent, и ReleaseComponent вызывает этот метод.
Примеры
Передача в асинхронную функцию простых данных
Следующая программа демонстрирует использование QueueAsyncCall. Если вы нажмете на кнопку CallButton, в LogListBox добавится 'Click 1', 'Click 2' и 'Async 1'. Обратите внимание, что асинхронный вызов выполняется только после завершения события CallButtonClick.
unit TestQueueAsyncCall;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, Buttons,
StdCtrls;
type
{ TQueueAsyncCallForm }
TQueueAsyncCallForm = class(TForm)
CallButton: TButton;
LogListBox: TListBox;
procedure CallButtonClick(Sender: TObject);
private
{ private declarations }
FCounter: PtrInt;
procedure Async(Data: PtrInt);
public
{ public declarations }
end;
var
QueueAsyncCallForm: TQueueAsyncCallForm;
implementation
{$R *.lfm}
{ TQueueAsyncCallForm }
procedure TQueueAsyncCallForm.CallButtonClick(Sender: TObject);
begin
LogListBox.Items.Add('Click 1');
FCounter := FCounter+1;
Application.QueueAsyncCall(@Async,FCounter);
LogListBox.Items.Add('Click 2');
end;
procedure TQueueAsyncCallForm.Async(Data: PtrInt);
begin
LogListBox.Items.Add('Async '+ IntToStr(Data));
end;
end.
Передача в асинхронную функцию записи
Вот некоторый код, извлеченный из модуля MemoChannel MultiLog, который используется для потоковой записи сообщений в журнал для управления Memo. Благодаря QueueAsyncCall() все записи в Memo сериализуются. Здесь нет блокировок и конфликтов, даже если сотни потоков вызывают метод Write().
...
type
TLogMsgData = record // запись может содержать гораздо больше, чем простая строка :-)
Text: string;
end;
PLogMsgData = ^TLogMsgData;
...
procedure TMemoChannel.Write(const AMsg: string);
var
LogMsgToSend: PLogMsgData;
begin
New(LogMsgToSend);
LogMsgToSend^.Text:= AMsg;
Application.QueueAsyncCall(@WriteAsyncQueue, PtrInt(LogMsgToSend)); // помещаем содержимое msg в очередь, которая будет обработана в основном потоке сразу после обработки всех других сообщений
end;
...
procedure TMemoChannel.WriteAsyncQueue(Data: PtrInt);
var // вызывается в основном потоке после обработки всех других сообщений, чтобы обеспечить потокобезопасный доступ к TMemo
ReceivedLogMsg: TLogMsgData;
begin
ReceivedLogMsg := PLogMsgData(Data)^;
try
if (FMemo <> nil) and (not Application.Terminated) then
begin
...
FMemo.Append(ReceivedLogMsg.Text) // <<< полностью потокобезопасен
end;
finally
Dispose(PLogMsgData(Data));
end;
end;