A linguagem
Lambdas, eventos e async
Funções anônimas com captura, eventos de objeto e a chamada guardada, e async/await sobre TTask.
Funções anônimas#
Uma função ou procedimento escrito no meio de uma expressão, sem nome, que pode capturar as variáveis do ponto onde nasce. O tipo é reference to:
type
TBinary = reference to function(A, B: Int32): Int32;
TAction = reference to procedure;program Lambdas;
type
TBinary = reference to function(A, B: Int32): Int32;
TAction = reference to procedure;
function Reduce(const AValues: array of Int32; AFunc: TBinary): Int32;
var
I: Int32;
begin
Result := AValues[0];
for I := 1 to High(AValues) do
Result := AFunc(Result, AValues[I]);
end function;
procedure Demo;
var
Counter: Int32;
Bump: TAction;
begin
var Sum := function(A, B: Int32): Int32;
begin
Exit(A + B);
end;
WriteLn('Sum: ', Sum(2, 3));
// corpo curto com =>
var Product := function(A, B: Int32): Int32; => Exit(A * B);
WriteLn('Reduce soma: ', Reduce([1, 2, 3, 4], Sum));
WriteLn('Reduce produto: ', Reduce([1, 2, 3, 4], Product));
// captura por referência: o fecho enxerga a variável do dono
Counter := 0;
Bump := procedure;
begin
Inc(Counter);
end;
Bump();
Bump();
Bump();
WriteLn('Counter: ', Counter);
end procedure;
begin
Demo;
end.Sum: 5
Reduce soma: 10
Reduce produto: 24
Counter: 3var F := function(…)…: o tipo é inferido, eF(2, 3)chama.- Corpo-seta:
function(A, B: Int32): Int32; => Exit(A * B)é a outra grafia do mesmo fecho. - Captura por referência: a variável local que um fecho toca deixa de morar no frame e passa a viver numa caixa no heap (um vetor dinâmico de um elemento). Os dois lados falam com a mesma caixa: o que a lambda escreve, o dono enxerga. Duas lambdas sobre a mesma variável usam uma caixa só.
- O fecho pode viver mais que a rotina que o criou: como a caixa tem contagem de referências, nada fica pendurado.
- Um fecho serve em qualquer lugar onde um
reference tocom a mesma assinatura é pedido (Reduce([1, 2, 3, 4], Sum)).
Eventos#
Um evento é um tipo procedural of object: um método com o objeto a que pertence.
type
TNotifyEvent = procedure(const ASender: TObject) of object;
TButton = class(TObject)
private
FOnClick: TNotifyEvent;
public
property OnClick: TNotifyEvent read FOnClick write FOnClick;
end class;A chamada guardada#
Evento!(args) chama o evento só se houver método ligado. Sem ele, nada acontece: nem a chamada, nem a avaliação dos argumentos. É o if Assigned(F) then F(...) do Delphi numa expressão só.
program Events;
type
TNotifyEvent = procedure(const ASender: TObject) of object;
TButton = class(TObject)
private
FOnClick: TNotifyEvent;
public
property OnClick: TNotifyEvent read FOnClick write FOnClick;
procedure Click;
end class;
TListener = class(TObject)
public
procedure Pressed(const ASender: TObject);
end class;
procedure TButton.Click;
begin
// chamada guardada: sem método ligado, nada acontece (nem os argumentos são avaliados)
FOnClick!(Self);
end procedure;
procedure TListener.Pressed(const ASender: TObject);
begin
WriteLn(' botão clicado');
end procedure;
var
Button: TButton;
Listener: TListener;
begin
Button := TButton.Create;
Listener := TListener.Create;
WriteLn('sem tratador:');
Button.Click;
Button.OnClick := Listener.Pressed;
WriteLn('com tratador:');
Button.Click;
end.sem tratador:
com tratador:
botão clicadoO evento que se desliga sozinho#
Delegate(T) resolve o problema do evento cujo alvo morre: veja Memória.
async e await#
async marca uma função ou um procedimento que roda numa task. A função devolve um TTask<T> (e o procedimento, um TTaskNoReturn); await espera pelo resultado.
program AsyncDemo;
type
TWorker = class(TObject)
public
async function Square(const N: Int32): Int32;
async function Fail(const N: Int32): Int32;
async procedure Hello;
end class;
async function TWorker.Square(const N: Int32): Int32;
begin
Exit(N * N);
end function;
async function TWorker.Fail(const N: Int32): Int32;
begin
raise Exception.Create('falhou com ' + IntToUStr(N));
end function;
async procedure TWorker.Hello;
begin
WriteLn('olá de dentro de uma task');
end procedure;
var
Worker: TWorker;
A, B, C, D: TTask<Int32>;
Done: TTaskNoReturn;
R1, R2: Int32;
begin
Worker := TWorker.Create;
// a task já começa a correr quando é criada (hot start)
A := Worker.Square(6);
B := Worker.Square(7);
WriteLn('resultado: ', await A, ' e ', await B);
Done := Worker.Hello;
await Done;
// esperar várias de uma vez: o fio dorme em todas ao mesmo tempo
C := Worker.Square(8);
D := Worker.Square(9);
R1, R2 := await C, D;
WriteLn('várias: ', R1, ' e ', R2);
// a exceção fica guardada na task e o await a dispara de novo
try
WriteLn(await Worker.Fail(3));
except
on E: Exception do
WriteLn('capturada no await: ', E.Message);
end try;
end.resultado: 36 e 49
olá de dentro de uma task
várias: 64 e 81
capturada no await: falhou com 3- As duas grafias dão no mesmo:
async function Venda: UStringequivale a uma função que devolveTTask<UString>criada com uma lambda. - A task já começa a correr quando é criada (hot start). Várias tasks no ar correm ao mesmo tempo, e o
awaitsó espera. R1, R2 := await C, D;espera várias de uma vez: o ganho é de espera, não de paralelismo, porque as tasks já estão correndo; o fio dorme em todos os eventos ao mesmo tempo, em vez de sondar um a um. O_descarta o resultado (é o que recebe a task de umasync procedure).- A exceção fica guardada na task e o
awaita dispara de novo, no ponto de quem espera. - Um
awaitdentro de uma task não enforca o pool de threads. - Uma task criada e nunca esperada é liberada sem problema.