Retorno de chamada (programação de computador) - Callback (computer programming)

Um retorno de chamada geralmente está de volta ao nível do chamador original.

Na programação de computadores , um retorno de chamada , também conhecido como um " call-depois " função , é qualquer código executável que é passado como um argumento para outro código; espera-se que outro código chame de volta (execute) o argumento em um determinado momento. Essa execução pode ser imediata, como em um retorno de chamada síncrono , ou pode acontecer posteriormente, como em um retorno de chamada assíncrono . Linguagens de programação oferecem suporte a retornos de chamada de maneiras diferentes, geralmente implementando-os com sub - rotinas , expressões lambda , blocos ou ponteiros de função .

Projeto

Existem dois tipos de callbacks, diferindo na forma como controlam o fluxo de dados em tempo de execução: callbacks de bloqueio (também conhecidos como callbacks síncronos ou apenas callbacks ) e callbacks adiados (também conhecidos como callbacks assíncronos ). Enquanto callbacks de bloqueio são invocados antes do retorno de uma função (no exemplo C abaixo, que ilustra um callback de bloqueio, é a função principal ), callbacks adiados podem ser invocados após o retorno de uma função. Callbacks adiados são freqüentemente usados ​​no contexto de operações de E / S ou tratamento de eventos, e são chamados por interrupções ou por um thread diferente no caso de vários threads. Devido à sua natureza, os retornos de chamada de bloqueio podem funcionar sem interrupções ou vários threads, o que significa que os retornos de chamada de bloqueio não são comumente usados ​​para sincronização ou delegação de trabalho a outro thread.

Callbacks são usados ​​para programar aplicativos em sistemas de janelas . Nesse caso, o aplicativo fornece (uma referência para) uma função de retorno de chamada personalizada específica para o sistema operacional chamar, que então chama essa função específica do aplicativo em resposta a eventos como cliques do mouse ou pressionamentos de tecla. Uma grande preocupação aqui é o gerenciamento de privilégios e segurança: embora a função seja chamada a partir do sistema operacional, ela não deve ser executada com o mesmo privilégio do sistema. Uma solução para este problema é o uso de anéis de proteção.

Implementação

A forma de um retorno de chamada varia entre as linguagens de programação :

  • Em assembly , C , C ++ , Pascal , Modula2 e linguagens semelhantes, um ponteiro em nível de máquina para uma função pode ser passado como um argumento para outra função (interna ou externa). Isso é suportado pela maioria dos compiladores e fornece a vantagem de usar diferentes linguagens juntas, sem bibliotecas ou classes de wrapper especiais. Um exemplo pode ser a API do Windows que é diretamente (mais ou menos) acessível por muitas linguagens, compiladores e montadores diferentes.
  • C ++ permite que os objetos forneçam sua própria implementação da operação de chamada de função. A Standard Template Library aceita esses objetos (chamados de functores ), bem como ponteiros de função, como parâmetros para vários algoritmos polimórficos.
  • Muitas linguagens dinâmicas , como JavaScript , Lua , Python , Perl e PHP , simplesmente permitem que um objeto de função seja passado.
  • Linguagens CLI como C # e VB.NET fornecem uma referência de encapsulamento de tipo seguro , um " delegado ", para definir ponteiros de função bem digitados . Eles podem ser usados ​​como retornos de chamada.
  • Eventos e manipuladores de eventos , conforme usados ​​em linguagens .NET, fornecem sintaxe generalizada para retornos de chamada.
  • As linguagens funcionais geralmente oferecem suporte a funções de primeira classe , que podem ser passadas como retornos de chamada para outras funções, armazenadas como dados ou retornadas de funções.
  • Algumas linguagens, como Algol 68 , Perl, Python, Ruby , Smalltalk , C ++ 11 e posterior, versões mais recentes de C # e VB.NET, bem como a maioria das linguagens funcionais, permitem que blocos de código sem nome ( expressões lambda ) sejam fornecidos em vez de referências a funções definidas em outro lugar.
  • Em algumas linguagens, por exemplo, Scheme , ML , JavaScript, Perl, Python, Smalltalk, PHP (desde 5.3.0), C ++ 11 e posterior, Java (desde 8) e muitas outras, tais funções podem ser fechamentos , ou seja, pode acessar e modificar variáveis ​​definidas localmente no contexto em que a função foi definida. Observe que o Java não pode, entretanto, modificar as variáveis ​​locais no escopo delimitador.
  • Em linguagens de programação orientadas a objetos sem argumentos com valor de função, como em Java antes de sua versão 8, os retornos de chamada podem ser simulados passando uma instância de uma classe abstrata ou interface, da qual o receptor chamará um ou mais métodos, enquanto o chama fim fornece uma implementação concreta. Esses objetos são efetivamente um pacote de retornos de chamada, mais os dados que eles precisam manipular. Eles são úteis na implementação de vários padrões de design , como Visitor , Observer e Strategy .

Usar

C

Os retornos de chamada têm uma ampla variedade de usos, por exemplo, na sinalização de erro: um programa Unix pode não querer terminar imediatamente ao receber o SIGTERM , portanto, para ter certeza de que seu encerramento é tratado corretamente, ele registraria a função de limpeza como um retorno de chamada. Callbacks também podem ser usados ​​para controlar se uma função atua ou não: Xlib permite que predicados personalizados sejam especificados para determinar se um programa deseja manipular um evento.

O código C a seguir demonstra o uso de retornos de chamada para exibir dois números.

#include <stdio.h>
#include <stdlib.h>

/* The calling function takes a single callback as a parameter. */
void PrintTwoNumbers(int (*numberSource)(void)) {
    int val1 = numberSource();
    int val2 = numberSource();
    printf("%d and %d\n", val1, val2);
}

/* A possible callback */
int overNineThousand(void) {
    return (rand()%1000) + 9001;
}

/* Another possible callback. */
int meaningOfLife(void) {
    return 42;
}

/* Here we call PrintTwoNumbers() with three different callbacks. */
int main(void) {
    time_t t;
    srand((unsigned)time(&t)); // Init seed for random function
    PrintTwoNumbers(&rand);
    PrintTwoNumbers(&overNineThousand);
    PrintTwoNumbers(&meaningOfLife);
    return 0;
}

Exemplo de saída:

25926 and 14941
9015 and 9630
42 and 42

Observe como isso é diferente de simplesmente passar a saída da função de retorno de chamada para a função de chamada, PrintTwoNumbers () - em vez de imprimir o mesmo valor duas vezes, o PrintTwoNumbers chama o retorno de chamada quantas vezes for necessário. Essa é uma das duas principais vantagens dos retornos de chamada.

A outra vantagem é que a função de chamada pode passar quaisquer parâmetros que desejar para as funções chamadas (não mostrado no exemplo acima). Isso permite ocultar informações corretas : o código que passa um retorno de chamada para uma função de chamada não precisa saber os valores dos parâmetros que serão passados ​​para a função. Se ele passasse apenas o valor de retorno, os parâmetros precisariam ser expostos publicamente.

Outro exemplo:

/*
 * This is a simple C program to demonstrate the usage of callbacks
 * The callback function is in the same file as the calling code.
 * The callback function can later be put into external library like
 * e.g. a shared object to increase flexibility.
 *
 */

#include <stdio.h>
#include <string.h>

typedef struct _MyMsg {
    int appId;
    char msgbody[32];
} MyMsg;

void myfunc(MyMsg *msg)
{
    if (strlen(msg->msgbody) > 0 )
        printf("App Id = %d \nMsg = %s \n",msg->appId, msg->msgbody);
    else
        printf("App Id = %d \nMsg = No Msg\n",msg->appId);
}

/*
 * Prototype declaration
 */
void (*callback)(MyMsg *);

int main(void)
{
    MyMsg msg1;
    msg1.appId = 100;
    strcpy(msg1.msgbody, "This is a test\n");

    /*
     * Assign the address of the function "myfunc" to the function
     * pointer "callback" (may be also written as "callback = &myfunc;")
     */
    callback = myfunc;

    /*
     * Call the function (may be also written as "(*callback)(&msg1);")
     */
    callback(&msg1);

    return 0;
}

A saída após a compilação:

$ gcc cbtest.c
$ ./a.out
App Id = 100
Msg = This is a test

Essa ocultação de informações significa que os retornos de chamada podem ser usados ​​durante a comunicação entre processos ou threads, ou por meio de comunicações serializadas e dados tabulares.

Em C ++, functor também é comumente usado ao lado do uso de ponteiro de função em C.

C #

Um retorno de chamada simples em C # :

public class Class1 
{
    static void Main(string[] args)
    {
        Class2 c2 = new Class2();
        
        /*
        * Calling method on Class2 with callback method as parameter
        */
        c2.Method(CallBackMethod);
    }

    /*
    * The callback method. This method prints the string sent in the callback
    */
    static void CallBackMethod(string str)
    {
        Console.WriteLine($"Callback was: {str}");
    }
}

public class Class2
{
    /*
    * The method that calls back to the caller. Takes an action (method) as parameter
    */
    public void Method(Action<string> callback)
    {
        /*
        * Calls back to method CallBackMet in Class1 with the message specified
        */
        callback("The message to send back");
    }
}

JavaScript

Callbacks são usados ​​na implementação de linguagens como JavaScript , incluindo suporte de funções JavaScript como callbacks por meio de js-ctypes e em componentes como addEventListener. No entanto, um exemplo nativo de retorno de chamada pode ser escrito sem nenhum código complexo:

function calculate(num1, num2, callbackFunction) {
    return callbackFunction(num1, num2);
}

function calcProduct(num1, num2) {
    return num1 * num2;
}

function calcSum(num1, num2) {
    return num1 + num2;
}
// alerts 75, the product of 5 and 15
alert(calculate(5, 15, calcProduct));
// alerts 20, the sum of 5 and 15
alert(calculate(5, 15, calcSum));

Primeiro, uma função calcular é definida com um parâmetro destinado ao retorno de chamada: callbackFunction . Em seguida, uma função que pode ser usada como um retorno de chamada para calcular é definida, calcProduct . Outras funções podem ser usadas para callbackFunction , como calcSum . Neste exemplo, calcule () é chamado duas vezes, uma vez com calcProduct como retorno de chamada e uma vez com calcSum . As funções retornam o produto e soma, respectivamente, e então o alerta irá exibi-los na tela.

Neste exemplo primitivo, o uso de um retorno de chamada é principalmente uma demonstração de princípio. Pode-se simplesmente chamar os callbacks como funções regulares, calcProduct (num1, num2) . Os retornos de chamada geralmente são usados ​​quando a função precisa realizar eventos antes que o retorno de chamada seja executado ou quando a função não tem (ou não pode) ter valores de retorno significativos para agir, como é o caso de JavaScript assíncrono (baseado em temporizadores) ou solicitações XMLHttpRequest . Exemplos úteis podem ser encontrados em bibliotecas JavaScript , como jQuery, onde o método .each () itera sobre um objeto semelhante a um array, o primeiro argumento sendo um retorno de chamada executado em cada iteração.

Vermelho e REBOL

A partir do JavaScript acima, aqui está como alguém poderia implementar o mesmo em REBOL ou Red (linguagem de programação) . Observe a apresentação mais limpa dos dados como código.

  • o retorno está implícito, pois o código em cada função é a última linha do bloco
  • Como o alerta requer uma string, o formulário produz uma string a partir do resultado do cálculo
  • A palavra de ordem! valores (ou seja,: calc-product e: calc-sum) acionam o interpretador para retornar o código da função em vez de avaliar com a função.
  • O tipo de dados! referências em um bloco! [flutuador! inteiro!] restringe o tipo de valores passados ​​como argumentos.
Red [Title: "Callback example"]

calculate: func [
    num1 [number!] 
    num2 [number!] 
    callback-function [function!]
][
    callback-function num1 num2
]

calc-product: func [
    num1 [number!] 
    num2 [number!]
][
    num1 * num2
]

calc-sum: func [
    num1 [number!] 
    num2 [number!]
][
    num1 + num2
]

; alerts 75, the product of 5 and 15
alert form calculate 5 15 :calc-product

; alerts 20, the sum of 5 and 15
alert form calculate 5 15 :calc-sum

Lua

Um exemplo de interpolação de cores usando o mecanismo Roblox que usa um retorno de chamada opcional .done:

wait(1)
local DT = wait()

function tween_color(object, finish_color, fade_time)
  local step_r = finish_color.r - object.BackgroundColor3.r
  local step_g = finish_color.g - object.BackgroundColor3.g
  local step_b = finish_color.b - object.BackgroundColor3.b
  local total_steps = 1/(DT*(1/fade_time))
  local completed;
  coroutine.wrap(function()
    for i = 0, 1, DT*(1 / fade_time) do
      object.BackgroundColor3 = Color3.new (
        object.BackgroundColor3.r + (step_r/total_steps),
        object.BackgroundColor3.g + (step_g/total_steps),
        object.BackgroundColor3.b + (step_b/total_steps)
      )
      wait()
    end
    if completed then
      completed()
    end
  end)()
  return {
    done = function(callback)
      completed = callback
    end
  }
end

tween_color(some_object, Color3.new(1, 0, 0), 1).done(function()
  print "Color tweening finished!"
end)

Pitão

Um uso clássico de callbacks em Python (e outras linguagens) é atribuir eventos a elementos de interface do usuário.

Aqui está um exemplo muito trivial do uso de um retorno de chamada em Python. Primeiro defina duas funções, o retorno de chamada e o código de chamada e, em seguida, passe a função de retorno de chamada para o código de chamada.

>>> def get_square(val):
...     """The callback."""
...     return val ** 2
...
>>> def caller(func, val):
...     return func(val)
...
>>> caller(get_square, 5)
25

Veja também

Referências

links externos