Vysypání výstupu

Školení, která pořádám

Při provádění dlouhotrvajících operací je vhodné uživatele informovat o tom, že se něco děje. Pokud operace sama neprodukuje žádný výstup, je možné v nějakých intervalech např. vypisovat tečky nebo posílat klientovi JavaScript, který zajistí aktualizaci ukazatele průběhu. Kvůli šetrnosti ale výstup skriptu neposílá PHP a webový server klientovi okamžitě, ale až když se ho nashromáždí větší množství. Pokud výstup chceme poslat ihned, musíme zavolat dvě funkce – ob_flush a flush. ob_flush zajistí vyprázdnění interního bufferovacího mechanismu PHP, flush vysype buffer webového serveru. Volání ob_flush si můžeme ušetřit při zapnutém ob_implicit_flush.

<?php
echo "Průběh: <span id='counter'></span>\n";
$total = 20;
$printed = 0;
for ($i=0; $i < $total; $i++) {
    sleep(rand(0, 2)); // dlouhotrvající operace
    if (time() >= $printed + 3 || $i+1 == $total) { // aktualizace počítadla nejdříve za tři vteřiny
        echo "<script type='text/javascript'>document.getElementById('counter').innerHTML = '" . round(100 * ($i+1) / $total) . "%';</script><noscript>.</noscript>\n";
        ob_flush();
        flush();
        $printed = time();
    }
}
?>

Skript v cyklu provádí dlouhotrvající operace a klientovi posílá JavaScriptový kód pro aktualizaci ukazatele průběhu. Aby se data neposílala zbytečně často, tak se kontroluje, jestli od posledního výpisu uběhly alespoň tři vteřiny – to se může hodit u operací, které ve skutečnosti tak dlouho netrvají, ale je jich třeba hodně.

Jakub Vrána, Výuka, 9.12.2005, diskuse: 20 (nové: 0)

Diskuse

Michal:

Co kdyz je to jedina operace ktera trva tuze dlouho? Pustis ji ve threadu a hlavni vlakno bude nadale zobrazovat tecky? Jdou v PHP vubec delat thready? Mozna by bylo lepsi tu operaci nejak pustit na pozadi, skript skoncit a pres AJAX se nejak rozumne dotazovat na prubeh. Shrnuto - tvoje reseni se mi vubec nelibi ;-)

ikona dgx:

nebo prostě pustit na výstup skript se setInterval, který bude zobrazovač průběhu aktualizovat sám, nezávisle na PHP, jenž si v klidu dokončí úkol.

Leo:

Presne podle hesla - co muze udelat klient necham udelat klienta. Jsem vsema dvaceti pro. Leo

ikona Jakub Vrána OpenID:

Ještě mi řekni, jak to chceš technicky realizovat.

Jak je z naznačeného příkladu patrné, dlouhotrvající operace obvykle netrvá konstantní dobu a JS bez PHP netuší, jak je daleko.

Michal Molhanec:

smysl ukazatelu prubehu/cinnosti je informovat o tom, ze se neco provadi, ze to nezmrzlo. generovat tuto generaci na klientovy nezavisle na prubehu PHP skriptu je tudiz dobre maximalne jako graficka atrapa

pepper:

zobrazeni prubehu je opravdu jen graficka atrapa protoze jeho smyslem vetsinou neni ukazovat stav prubehu. Takze tupa animace navodi uzivateli pocit ze se neco deje a klient se v intervalech taze na vysledek a zachova se podle toho.
IMO je tedy sparvna kombinace obojiho.

ikona Jakub Vrána OpenID:

Thready v PHP nejsou a oficiálně nikdy brzy ani nebudou. Pouštění na pozadí je také problematické.

Mordae:

A nedal by se zobrazovat prubeh pres ticks?

<?php
function status ( )
{
        echo '.';
        flush();
}

register_tick_function('status');
declare(
ticks=1)
{
        // slozita operace
}
unregister_tick_function('status');
?>

Samozrejme by to chtelo vyladit, nekde si skladovat status a nepsat tecky po kazdem prikazu.

ikona Jakub Vrána OpenID:

Šlo by to taky, ale se dvěmi výhradami. Ticks neurčují počet vteřin, ale počet elementárních operací, takže hodnota 1 je příliš malá. Hlavní pointa článku byla, že samotné flush() při vypnutém ob_implicit_flush() nestačí, takže by to chtělo doplnit ještě ob_flush().

lukas:

A neni pri prenosu ten cely HTML soubor nejak komprimovan? V takovem pripade bych totiz rekl, ze Apache stejne vsechna data podrzi az do skonceni skriptu a odesle je az cela (male datove objemy se spatne komprimuji). Pak by se OB funkce hodily jen na premluveni PHPka ve stylu "jeste ten kod nevypisuj, budu si nekdy ve prostred behu skriptu chtit poslat par cookies klientovi".

ikona Jakub Vrána OpenID:

Data se při přenosu komprimovat mohou. Většina kompresních algoritmů je ale proudových - z dat na vstupu ihned produkují výstup a problém je tak maximálně se zarovnáním nedokončených bajtů.

S obvyklým ob_gzhandler vysypání výstupu bez problémů funguje.

Paja5:

Je to zajimavy clanek. Nemate prosim nekdo navrh jak to rozjet pod Operou?

Gimli2:

Kdysi na Builder.cz psali:
... Výsledkem je provedení náročné akce, ovšem uživatel musí čekat do konce a jím případně vyvolané přerušení vše ukončí.

Druhou použitou funkcí je ignore_user_abort(). Ta zajistí ignoraci přerušení vyvolaného uživatelem. Resp. stránka se přestane natahovat, ale skript na straně serveru běží dál. (1 - zapne ignoraci, 0 - vypne, bez parametru - vrátí aktuální stav)

<?php
/* STOP tlacitko z prohlizece ignorujeme ..*/
ignore_user_abort(1);

/* timeout vypnut */
set_time_limit(0);

/* fork na jinou stranku */
Header("Location: /nekam_jinam.html");
Header("Connection: close\n\n");

/* donutime jej poslat hlavicku.. */
echo "neco";
flush();

/*** fork proveden */

/* co se ma provadet dal .. */

/* narocne vypocty :) */
for($i=0; $i<3000000; $i++) {/* */};

/* kontrola ze vse provedeno.. */
mail("spam@spam.spam","test","TEST, i=$i");
?>

ikona Jakub Vrána OpenID:

Pěkný trik. Ještě uvedu odkaz na původní článek: http://www.builder.cz/art/homepage/clanek1080749127.html.

pa3k:

Stretol sa niekto s nefunkčnosťou pri zapnutej kompresii? http://www.php.net/manual/en/function.flush.php#59066 Testoval som to a pri zapnutej kompresii to naozaj nefunguje.
Nedá sa to nejak ovplyvniť priamo z php bez nutnosti editovať .htaccess?

pa3k:

Vyriešené pomocou .htaccess:
<Files "script.php">
SetEnv no-gzip
</Files>
querystring ma až tak moc netrápi, ak by bolo treba už si kompresiu nejako ošetrim (zapnutím) na úrovni php

ikona armin:

Dobry den,
co se tyce funkcnosti i zde popsaneho prikladu v prohlizeci Opera, je nutno uvest ze je potrebne na konec (cyklicky vypisovaneho "prirastku") pridat zalomeni radku: <br />. A aby nedoslo k rozhozeni designu, staci doplnit styl:
<br style="line-height: 0px;" />

ikona Nox:

V Opeře 9.52 mi to bohužel nefuguje vůbec, až při zastavení uživatelem se vypíše aktuální stav. V nastavení mám dáno "vykreslovat okamžitě". Ve FF3 funguje v pohodě.

Kajman:

Webový štít od avg dá prohlížeči až celou stránku.

ikona Martin Adámek:

Nouzové řešení pro ošetření funkce i s AVG:

http://www.adamek.cz/sw/php/flush-vyplach-vypis-nefunguje-avg/

Diskuse je zrušena z důvodu spamu.

avatar © 2005-2024 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.