Chamada de função assíncrona em PHP

86

Estou trabalhando em um aplicativo da web PHP e preciso executar algumas operações de rede na solicitação, como buscar alguém no servidor remoto com base na solicitação do usuário.

É possível simular o comportamento assíncrono em PHP, visto que tenho que passar alguns dados para uma função e também preciso de saída dela.

Meu código é como:

<?php

     $data1 = processGETandPOST();
     $data2 = processGETandPOST();
     $data3 = processGETandPOST();

     $response1 = makeNetworkCall($data1);
     $response2 = makeNetworkCall($data2);
     $response3 = makeNetworkCall($data3);

     processNetworkResponse($response1);
     processNetworkResponse($response2);
     processNetworkResponse($response3);

     /*HTML and OTHER UI STUFF HERE*/

     exit;
?>

Cada operação de rede leva cerca de 5 segundos para ser concluída, adicionando um total de 15 segundos ao tempo de resposta do meu aplicativo, desde que eu faça 3 solicitações.

A função makeNetworkCall () apenas faz uma solicitação HTTP POST.

O servidor remoto é uma API de terceiros, então não tenho nenhum controle sobre ele.

PS: Por favor, não responda dando sugestões sobre AJAX ou outras coisas. Atualmente, estou procurando se posso fazer isso através do PHP pode ser com uma extensão C ++ ou algo parecido.

Hardeep Singh
fonte
Tente usar CURLpara disparar solicitações e buscar alguns dados da web ...
Bogdan Burym
Acredito que a resposta esteja aqui: stackoverflow.com/questions/13846192/… Observação rápida: use threading
DRAX
possível duplicata de chamada de threading
CRABOLO
Você pode usar a função stream_select do PHP para executar código sem bloqueio. Reagir usa isso para criar um loop de event-driven semelhante ao node.js .
Quinn Comendant

Respostas:

20

Hoje em dia, é melhor usar filas do que threads (para quem não usa o Laravel, existem toneladas de outras implementações como essa ).

A ideia básica é que seu script PHP original coloca tarefas ou trabalhos em uma fila. Então, você tem trabalhadores de tarefa de fila em execução em outro lugar, tirando tarefas da fila e começa a processá-los independentemente do PHP original.

As vantagens são:

  1. Escalabilidade - você pode simplesmente adicionar nós de trabalho para acompanhar a demanda. Desta forma, as tarefas são executadas em paralelo.
  2. Confiabilidade - gerenciadores de filas modernos, como RabbitMQ, ZeroMQ, Redis, etc, são feitos para serem extremamente confiáveis.
aljo f
fonte
8

Não tenho uma resposta direta, mas você pode querer investigar estas coisas:

Sebastiaan Hilbers
fonte
3

cURL vai ser sua única escolha real aqui (ou isso, ou usando soquetes sem bloqueio e alguma lógica personalizada).

Este link deve encaminhá-lo na direção certa. Não há processamento assíncrono em PHP, mas se você estiver tentando fazer várias solicitações da web simultâneas, cURL multi cuidará disso para você.

Colin M
fonte
2

Acho que se o HTML e outras coisas da interface do usuário precisam dos dados retornados, não haverá uma maneira de assíncroná-los.

Acredito que a única maneira de fazer isso em PHP seria registrar uma solicitação em um banco de dados e ter uma verificação cron a cada minuto, ou usar algo como processamento de fila Gearman, ou talvez exec () um processo de linha de comando

Nesse ínterim, sua página php teria que gerar algum html ou js que o faz recarregar a cada poucos segundos para verificar o progresso, o que não é o ideal.

Para contornar o problema, quantas solicitações diferentes você está esperando? Você poderia baixar todos eles automaticamente a cada hora ou assim e salvá-los em um banco de dados?

CodeMonkey
fonte
0

Acho que algum código sobre a solução cURL é necessário aqui, então vou compartilhar o meu (ele foi escrito misturando várias fontes como o Manual do PHP e comentários).

Ele faz algumas solicitações HTTP paralelas (domínios em $aURLs) e imprime as respostas assim que cada uma é concluída (e as armazena $donepara outros usos possíveis).

O código é mais longo do que o necessário devido à parte impressa em tempo real e ao excesso de comentários, mas fique à vontade para editar a resposta para melhorá-la:

<?php
/* Strategies to avoid output buffering, ignore the block if you don't want to print the responses before every cURL is completed */
ini_set('output_buffering', 'off'); // Turn off output buffering
ini_set('zlib.output_compression', false); // Turn off PHP output compression       
//Flush (send) the output buffer and turn off output buffering
ob_end_flush(); while (@ob_end_flush());        
apache_setenv('no-gzip', true); //prevent apache from buffering it for deflate/gzip
ini_set('zlib.output_compression', false);
header("Content-type: text/plain"); //Remove to use HTML
ini_set('implicit_flush', true); // Implicitly flush the buffer(s)
ob_implicit_flush(true);
header('Cache-Control: no-cache'); // recommended to prevent caching of event data.
$string=''; for($i=0;$i<1000;++$i){$string.=' ';} output($string); //Safari and Internet Explorer have an internal 1K buffer.
//Here starts the program output

function output($string){
    ob_start();
    echo $string;
    if(ob_get_level()>0) ob_flush();
    ob_end_clean();  // clears buffer and closes buffering
    flush();
}

function multiprint($aCurlHandles,$print=true){
    global $done;
    // iterate through the handles and get your content
    foreach($aCurlHandles as $url=>$ch){
        if(!isset($done[$url])){ //only check for unready responses
            $html = curl_multi_getcontent($ch); //get the content           
            if($html){
                $done[$url]=$html;
                if($print) output("$html".PHP_EOL);
            }           
        }
    }
};

function full_curl_multi_exec($mh, &$still_running) {
    do {
      $rv = curl_multi_exec($mh, $still_running); //execute the handles 
    } while ($rv == CURLM_CALL_MULTI_PERFORM); //CURLM_CALL_MULTI_PERFORM means you should call curl_multi_exec() again because there is still data available for processing
    return $rv;
} 

set_time_limit(60); //Max execution time 1 minute

$aURLs = array("http://domain/script1.php","http://domain/script2.php");  // array of URLs

$done=array();  //Responses of each URL

    //Initialization
    $aCurlHandles = array(); // create an array for the individual curl handles
    $mh = curl_multi_init(); // init the curl Multi and returns a new cURL multi handle
    foreach ($aURLs as $id=>$url) { //add the handles for each url        
        $ch = curl_init(); // init curl, and then setup your options
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER,1); // returns the result - very important
        curl_setopt($ch, CURLOPT_HEADER, 0); // no headers in the output
        $aCurlHandles[$url] = $ch;
        curl_multi_add_handle($mh,$ch);
    }

    //Process
    $active = null; //the number of individual handles it is currently working with
    $mrc=full_curl_multi_exec($mh, $active); 
    //As long as there are active connections and everything looks OK…
    while($active && $mrc == CURLM_OK) { //CURLM_OK means is that there is more data available, but it hasn't arrived yet.  
        // Wait for activity on any curl-connection and if the network socket has some data…
        if($descriptions=curl_multi_select($mh,1) != -1) {//If waiting for activity on any curl_multi connection has no failures (1 second timeout)     
            usleep(500); //Adjust this wait to your needs               
            //Process the data for as long as the system tells us to keep getting it
            $mrc=full_curl_multi_exec($mh, $active);        
            //output("Still active processes: $active".PHP_EOL);        
            //Printing each response once it is ready
            multiprint($aCurlHandles);  
        }
    }

    //Printing all the responses at the end
    //multiprint($aCurlHandles,false);      

    //Finalize
    foreach ($aCurlHandles as $url=>$ch) {
        curl_multi_remove_handle($mh, $ch); // remove the handle (assuming  you are done with it);
    }
    curl_multi_close($mh); // close the curl multi handler
?>
Leopoldo Sanczyk
fonte
0

Uma maneira é usar pcntl_fork()em uma função recursiva.

function networkCall(){
  $data = processGETandPOST();
  $response = makeNetworkCall($data);
  processNetworkResponse($response);
  return true;
}

function runAsync($times){
  $pid = pcntl_fork();
  if ($pid == -1) {
    die('could not fork');
  } else if ($pid) {
    // we are the parent
    $times -= 1;
    if($times>0)
      runAsync($times);
    pcntl_wait($status); //Protect against Zombie children
  } else {
    // we are the child
    networkCall();
    posix_kill(getmypid(), SIGKILL);
  }
}

runAsync(3);

Uma coisa pcntl_fork()é que, ao executar o script por meio do Apache, ele não funciona (não é suportado pelo Apache). Então, uma maneira de resolver esse problema é rodar o script usando o php cli, como: exec('php fork.php',$output);de outro arquivo. Para fazer isso, você terá dois arquivos: um que é carregado pelo Apache e outro que é executado exec()de dentro do arquivo carregado pelo Apache assim:

apacheLoadedFile.php

exec('php fork.php',$output);

fork.php

function networkCall(){
  $data = processGETandPOST();
  $response = makeNetworkCall($data);
  processNetworkResponse($response);
  return true;
}

function runAsync($times){
  $pid = pcntl_fork();
  if ($pid == -1) {
    die('could not fork');
  } else if ($pid) {
    // we are the parent
    $times -= 1;
    if($times>0)
      runAsync($times);
    pcntl_wait($status); //Protect against Zombie children
  } else {
    // we are the child
    networkCall();
    posix_kill(getmypid(), SIGKILL);
  }
}

runAsync(3);
JVE999
fonte