Zpětné volání (počítačové programování) - Callback (computer programming)

Zpětné volání je často zpět na úrovni původního volajícího.

V programování počítače , na zpětné volání , také známý jako „ call-pofunkci , je jakýkoli spustitelný kód , který je předán jako argument, k jiným kódem; tento další kód se očekává, že v danou dobu zavolá zpět (provede) argument. Toto provedení může být okamžité jako v synchronním zpětném volání , nebo se to může stát později v čase jako v asynchronním zpětném volání . Programovací jazyky podporují zpětná volání různými způsoby, často je implementují pomocí podprogramů , výrazů lambda , bloků nebo ukazatelů funkcí .

Design

Existují dva typy zpětných volání, které se liší v tom, jak řídí tok dat za běhu: blokování zpětných volání (známé také jako synchronní zpětná volání nebo jen zpětná volání ) a odložené zpětné volání (také známé jako asynchronní zpětná volání ). Zatímco blokující zpětná volání jsou vyvolána před návratem funkce (v příkladu C níže, který ilustruje blokující zpětné volání, je to hlavní funkce ), odložená zpětná volání mohou být vyvolána po návratu funkce. Odložená zpětná volání se často používají v kontextu I / O operací nebo zpracování událostí a jsou volána přerušeními nebo jiným vláknem v případě více vláken. Z důvodu své povahy mohou blokovací zpětná volání fungovat bez přerušení nebo více vláken, což znamená, že blokující zpětná volání se běžně nepoužívají pro synchronizaci nebo delegování práce na jiné vlákno.

Zpětná volání se používají k programování aplikací v okenních systémech . V tomto případě aplikace dodává (odkaz na) konkrétní vlastní funkci zpětného volání pro volání operačního systému, která pak volá tuto funkci specifickou pro aplikaci v reakci na události, jako jsou kliknutí myší nebo stisknutí kláves. Hlavním problémem zde je správa oprávnění a zabezpečení: zatímco je funkce volána z operačního systému, neměla by běžet se stejnými oprávněními jako systém. Řešením tohoto problému je použití ochranných kroužků .

Implementace

Forma zpětného volání se u různých programovacích jazyků liší :

  • V montáž , C , C ++ , Pascal , Modula-2 a podobné jazyky, stroj na úrovni ukazatel na funkci, může být předán jako argument k jiné funkci (vnitřní nebo vnější). To je podporováno většinou překladačů a poskytuje výhodu společného používání různých jazyků bez speciálních souhrnných knihoven nebo tříd. Jedním příkladem může být Windows API, které je přímo (víceméně) přístupné mnoha různým jazykům, překladačům a assemblerům.
  • C ++ umožňuje objektům poskytovat vlastní implementaci operace volání funkce. Knihovna standardních šablon přijímá tyto objekty (nazývané funktory ), stejně jako ukazatele funkcí, jako parametry různých polymorfních algoritmů.
  • Mnoho dynamických jazyků , jako je JavaScript , Lua , Python , Perl a PHP , jednoduše umožňuje procházení funkčních objektů.
  • Jazyky CLI, jako je C # a VB.NET, poskytují typově bezpečný zapouzdřovací odkaz, „ delegáta “, který definuje dobře napsané funkční ukazatele . Lze je použít jako zpětná volání.
  • Události a obslužné rutiny událostí , jak se používají v jazycích .NET, poskytují zobecněnou syntaxi pro zpětná volání.
  • Funkční jazyky obecně podporují prvotřídní funkce , které lze předat jako zpětná volání jiným funkcím, uložit jako data nebo vrátit z funkcí.
  • Některé jazyky, jako Algol 68 , Perl, Python, Ruby , Smalltalk , C ++ 11 a novější, novější verze C # a VB.NET i většina funkčních jazyků, umožňují dodávat nepojmenované bloky kódu ( výrazy lambda ) místo odkazů na funkce definované jinde.
  • V některých jazycích, např. Scheme , ML , JavaScript, Perl, Python, Smalltalk, PHP (od 5.3.0), C ++ 11 a novější, Java (od 8), a mnoho dalších, mohou být takové funkce uzávěry , tj. může přistupovat a měnit proměnné lokálně definované v kontextu, ve kterém byla funkce definována. Všimněte si, že Java však nemůže upravovat lokální proměnné v uzavřeném rozsahu.
  • V objektově orientovaných programovacích jazycích bez funkčních argumentů, jako je tomu v Javě před jeho verzí 8, lze zpětná volání simulovat předáním instance abstraktní třídy nebo rozhraní, z nichž příjemce bude volat jednu nebo více metod, zatímco volání end poskytuje konkrétní implementaci. Takové objekty jsou ve skutečnosti svazek zpětných volání plus data, která potřebují k manipulaci. Jsou užitečné při implementaci různých návrhových vzorů, jako jsou Visitor , Observer a Strategy .

Použití

C

Zpětná volání mají širokou škálu použití, například při signalizaci chyb: Unixový program nemusí chtít okamžitě ukončit, když obdrží SIGTERM , takže aby se ujistil, že jeho ukončení je zpracováno správně, zaregistroval by funkci vyčištění jako zpětné volání. Zpětná volání lze také použít k řízení, zda funkce funguje nebo ne: Xlib umožňuje specifikovat vlastní predikáty k určení, zda si program přeje zpracovat událost.

Následující kód C ukazuje použití zpětných volání k zobrazení dvou čísel.

#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;
}

Příklad výstupu:

25926 and 14941
9015 and 9630
42 and 42

Všimněte si, jak se to liší od jednoduchého předání výstupu funkce zpětného volání do volající funkce PrintTwoNumbers () - namísto tisku stejné hodnoty dvakrát PrintTwoNumbers volá zpětné volání tolikrát, kolikrát vyžaduje. To je jedna ze dvou hlavních výhod zpětných volání.

Druhou výhodou je, že volající funkce může předat volaným funkcím libovolné parametry, které si přeje (není zobrazeno ve výše uvedeném příkladu). To umožňuje správné skrytí informací : kód, který předá zpětné volání volající funkci, nemusí znát hodnoty parametrů, které budou předány funkci. Pokud by to jen prošlo návratovou hodnotou, pak by parametry musely být vystaveny veřejně.

Další příklad:

/*
 * 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;
}

Výstup po kompilaci:

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

Tato skrytí informací znamená, že zpětná volání lze použít při komunikaci mezi procesy nebo vlákny nebo prostřednictvím serializované komunikace a tabulkových dat.

V C ++ se funktor také běžně používá vedle použití ukazatele funkce v C.

C#

Jednoduché zpětné volání v 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

Zpětná volání se používají při implementaci jazyků, jako je JavaScript , včetně podpory funkcí JavaScript jako zpětná volání prostřednictvím js-ctypes a v komponentách, jako je addEventListener. Nativní příklad zpětného volání však lze napsat bez jakéhokoli složitého kódu:

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));

Nejprve je definován výpočet funkce s parametrem určeným pro zpětné volání: callbackFunction . Pak je definována funkce, kterou lze použít jako zpětné volání k výpočtu , calcProduct . Pro callbackFunction mohou být použity další funkce , například calcSum . V tomto příkladu je Calc () vyvolána dvakrát, jednou s calcProduct jako zpětné volání a jednou s calcSum . Funkce vrátí produkt, respektive součet, a poté je výstraha zobrazí na obrazovku.

V tomto primitivním příkladu je použití zpětného volání primárně ukázkou principu. Dalo by se jednoduše zavolat zpětná volání jako běžné funkce, calcProduct (num1, num2) . Zpětná volání se obecně používají, když funkce potřebuje provést události před provedením zpětného volání, nebo když funkce nemá (nebo nemůže) mít smysluplné návratové hodnoty, na které by měla reagovat, jako je tomu v případě asynchronního JavaScriptu (na základě časovačů) nebo požadavků XMLHttpRequest . Užitečné příklady lze najít v knihovnách JavaScriptu, jako je jQuery, kde metoda .each () iteruje přes objekt podobný poli, přičemž prvním argumentem je zpětné volání, které se provádí při každé iteraci.

Červená a REBOL

Z výše uvedeného JavaScriptu je zde uvedeno, jak by bylo možné implementovat to samé buď v REBOLu, nebo v Redu (programovací jazyk) . Všimněte si čistší prezentace dat jako kódu.

  • návrat je implikován, protože kód v každé funkci je posledním řádkem bloku
  • Protože výstraha vyžaduje řetězec, formulář vytvoří řetězec z výsledku výpočtu
  • Úvodní slovo! hodnoty (tj.: calc-product a: calc-sum) spouští tlumočník, aby vrátil kód funkce, místo aby ji vyhodnotil pomocí funkce.
  • Datový typ! odkazy v bloku! [plovák! integer!] omezit typ hodnot předávaných jako argumenty.
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

Příklad barevného doplnění využívající modul Roblox, který přebírá volitelné zpětné volání .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)

Krajta

Klasickým použitím zpětných volání v Pythonu (a dalších jazycích) je přiřazování událostí prvkům uživatelského rozhraní.

Zde je velmi triviální příklad použití zpětného volání v Pythonu. Nejprve definujte dvě funkce, zpětné volání a volací kód, poté předejte funkci zpětného volání do volacího kódu.

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

Viz také

Reference

externí odkazy