這可能是誘人的,只要將async
,await
和Awaitable
放在你所有的代碼里。雖然可以有更多的async
功能 - 事實(shí)上,你一般不應(yīng)該害怕做一個(gè)功能,async
因?yàn)闆](méi)有性能損失這樣做 - 有一些準(zhǔn)則,你應(yīng)該遵循,以最大限度地發(fā)揮有效利用async
。
如果你正在努力為您的代碼是否應(yīng)該是Async與否,通??梢蚤_(kāi)始尋找答案肯定,并找到一個(gè)理由說(shuō)沒(méi)有。例如,一個(gè)簡(jiǎn)單的hello world程序可以用Async處理,沒(méi)有性能損失。您可能無(wú)法獲得任何收益,但您不會(huì)收到任何損失 - 它將針對(duì)任何可能需要Async的更改進(jìn)行設(shè)置。
這兩個(gè)程序是為了所有意圖和目的,等同的。
<?hh
namespace Hack\UserDocumentation\Async\Guidelines\Examples\NonAsyncHello;
function get_hello(): string {
return "Hello";
}
function run_na_hello(): void {
var_dump(get_hello());
}
run_na_hello();
Output
string(5) "Hello"
<?hh
namespace Hack\UserDocumentation\Async\Guidelines\Examples\Hello;
async function get_hello(): Awaitable<string> {
return "Hello";
}
async function run_a_hello(): Awaitable<void> {
$x = await get_hello();
var_dump($x);
}
run_a_hello();
Output
string(5) "Hello"
只要確保你遵循其余的準(zhǔn)則。Async非常好,但您仍然需要考慮緩存,批量和效率等方面。
對(duì)于A(yíng)sync將提供最大效益的常見(jiàn)情況,HHVM提供方便的擴(kuò)展庫(kù),以幫助編寫(xiě)代碼更容易。根據(jù)您的用例情況,您應(yīng)該自由使用:
如果您只記住一條規(guī)則,請(qǐng)記住:
**不要await循環(huán)**
它完全違反了Async的目的。
<?hh
namespace Hack\UserDocumentation\Async\Guidelines\Examples\Loop;
class User {
public string $name;
protected function __construct(string $name) { $this->name = $name; }
static function get_name(int $id): User {
return new User(str_shuffle("ABCDEFGHIJ") . strval($id));
}
}
async function load_user(int $id): Awaitable<User> {
// Load user from somewhere (e.g., database).
// Fake it for now
return User::get_name($id);
}
async function load_users_await_loop(array<int> $ids): Awaitable<Vector<User>> {
$result = Vector {};
foreach ($ids as $id) {
$result[] = await load_user($id);
}
return $result;
}
function runMe(): void {
$ids = array(1, 2, 5, 99, 332);
$result = \HH\Asio\join(load_users_await_loop($ids));
var_dump($result[4]->name);
}
runMe();
Output
string(13) "JFHBIAEDGC332"
在上面的例子中,循環(huán)正在做兩件事情:
相反,您將需要使用我們的Async感知映射功能vm()。
<?hh
namespace Hack\UserDocumentation\Async\Guidelines\Examples\NoLoop;
class User {
public string $name;
protected function __construct(string $name) { $this->name = $name; }
static function get_name(int $id): User {
return new User(str_shuffle("ABCDEFGHIJ") . strval($id));
}
}
async function load_user(int $id): Awaitable<User> {
// Load user from somewhere (e.g., database).
// Fake it for now
return User::get_name($id);
}
async function load_users_no_loop(array<int> $ids): Awaitable<Vector<User>> {
return await \HH\Asio\vm(
$ids,
fun('\Hack\UserDocumentation\Async\Guidelines\Examples\NoLoop\load_user')
);
}
function runMe(): void {
$ids = array(1, 2, 5, 99, 332);
$result = \HH\Asio\join(load_users_no_loop($ids));
var_dump($result[4]->name);
}
runMe();
Output
string(13) "AJBIHCDGFE332"
學(xué)習(xí)如何構(gòu)建Async代碼最重要的方面是理解數(shù)據(jù)依賴(lài)關(guān)系模式。以下是如何確保Async代碼是數(shù)據(jù)依賴(lài)性的一般流程:
假設(shè)我們正在收到作者的博客文章。這將涉及以下步驟:
<?hh
namespace Hack\UserDocumentation\Async\Guidelines\Examples\DataDependencies;
// So we can use asio-utilities function vm()
class PostData {
// using constructor argument promotion
public function __construct(public string $text) {}
}
async function fetch_all_post_ids_for_author(int $author_id)
: Awaitable<array<int>> {
// Query database, etc., but for now, just return made up stuff
return array(4, 53, 99);
}
async function fetch_post_data(int $post_id): Awaitable<PostData> {
// Query database, etc. but for now, return something random
return new PostData(str_shuffle("ABCDEFGHIJKLMNOPQRSTUVWXYZ"));
}
async function fetch_comment_count(int $post_id): Awaitable<int> {
// Query database, etc., but for now, return something random
return rand(0, 50);
}
async function fetch_page_data(int $author_id)
: Awaitable<Vector<(PostData, int)>> {
$all_post_ids = await fetch_all_post_ids_for_author($author_id);
// An async closure that will turn a post ID into a tuple of
// post data and comment count
$post_fetcher = async function(int $post_id): Awaitable<(PostData, int)> {
list($post_data, $comment_count) =
await \HH\Asio\v(array(
fetch_post_data($post_id),
fetch_comment_count($post_id),
));
/* The problem is that v takes Traverable<Awaitable<T>> and returns
* Awaitable<Vector<T>>, but there isn't a good value of T that represents
* both ints and PostData, so they're currently almost a union type.
*
* Now we need to tell the typechecker what's going on.
* In the future, we plan to add HH\Asio\va() - VarArgs - to support this.
* This will have a type signature that varies depending on the number of
* arguments, for example:
*
* - va(Awaitable<T1>, Awaitable<T2>): Awaitable<(T1, T2)>
* - va(Awaitable<T1>,
* Awaitable<T2>,
* Awaitable<T3>): Awaitable<(T1, T2, T3)>
*
* And so on, with no need for T1, T2, ... Tn to be related types.
*/
invariant($post_data instanceof PostData, "This is good");
invariant(is_int($comment_count), "This is good");
return tuple($post_data, $comment_count);
};
// Transform the array of post IDs into an array of results,
// using the vm() function from asio-utilities
return await \HH\Asio\vm($all_post_ids, $post_fetcher);
}
async function generate_page(int $author_id): Awaitable<string> {
$tuples = await fetch_page_data($author_id);
$page = "";
foreach ($tuples as $tuple) {
list($post_data, $comment_count) = $tuple;
// Normally render the data into HTML, but for now, just create a
// normal string
$page .= $post_data->text . " " . $comment_count . PHP_EOL;
}
return $page;
}
$page = \HH\Asio\join(generate_page(13324)); // just made up a user id
var_dump($page);
Output
string(89) "AGEDMJQTFIVSCPHKLURWXNOZBY 9
ALSJURTKYIFBQMHXPNVWCDGZOE 25
GFMEYPITXDBORLVCKNAWJSUZQH 10
"
上面的例子遵循我們的流程:
等待手柄可以重新安排。這意味著它將被發(fā)回到調(diào)度程序的隊(duì)列,等待直到其他等待運(yùn)行。批處理可以很好地利用重新安排。例如,假設(shè)您有高延遲查詢(xún)數(shù)據(jù),但您可以在單個(gè)請(qǐng)求中發(fā)送多個(gè)密鑰進(jìn)行查找。
<?hh
namespace Hack\UserDocumentation\Async\Guidelines\Examples\Batching;
// For asio-utilities function later(), etc.
async function b_one(string $key): Awaitable<string> {
$subkey = await Batcher::lookup($key);
return await Batcher::lookup($subkey);
}
async function b_two(string $key): Awaitable<string> {
return await Batcher::lookup($key);
}
async function batching(): Awaitable<void> {
$results = await \HH\Asio\v(array(b_one('hello'), b_two('world')));
echo $results[0] . PHP_EOL;
echo $results[1];
}
\HH\Asio\join(batching());
class Batcher {
private static array<string> $pendingKeys = array();
private static ?Awaitable<array<string, string>> $aw = null;
public static async function lookup(string $key): Awaitable<string> {
// Add this key to the pending batch
self::$pendingKeys[] = $key;
// If there's no awaitable about to start, create a new one
if (self::$aw === null) {
self::$aw = self::go();
}
// Wait for the batch to complete, and get our result from it
$results = await self::$aw;
return $results[$key];
}
private static async function go(): Awaitable<array<string, string>> {
// Let other awaitables get into this batch
await \HH\Asio\later();
// Now this batch has started; clear the shared state
$keys = self::$pendingKeys;
self::$pendingKeys = array();
self::$aw = null;
// Do the multi-key roundtrip
return await multi_key_lookup($keys);
}
}
async function multi_key_lookup(array<string> $keys)
: Awaitable<array<string, string>> {
// lookup multiple keys, but, for now, return something random
$r = array();
foreach ($keys as $key) {
$r[$key] = str_shuffle("ABCDEF");
}
return $r;
}
Output
/data/users/joelm/fbsource-opt/fbcode/_bin/hphp/hhvm/hhvm
BEACFD
FDCEBA
在上面的例子中,我們將包含數(shù)據(jù)信息的服務(wù)器的往返次數(shù)減少到兩個(gè),通過(guò)批處理第一個(gè)查找b_one()和查找b_two()。該Batcher::lookup()功能有助于實(shí)現(xiàn)這一減少。
將await HH\Asio\later()在Batcher::go()基本上允許Batcher::go()推遲到其他未決awaitables已經(jīng)運(yùn)行。
所以,await HH\Asio\v(array(b_one..., b_two...));有兩個(gè)待決的等待。如果b_one()被稱(chēng)為第一個(gè),它調(diào)用Batcher::lookup(),哪個(gè)調(diào)用Batcher::go(),哪些重新調(diào)度通過(guò)later()。然后HHVM尋找其他待處理的等待。b_two()也正在等待。它調(diào)用Batcher::lookup(),然后它就會(huì)通過(guò)暫停await self::$aw,因?yàn)锽atcher::$aw不是null任何更長(zhǎng)的時(shí)間?,F(xiàn)在Batcher::go()恢復(fù),獲取并返回結(jié)果。
你覺(jué)得在這里發(fā)生什么?
<?hh
namespace Hack\UserDocumentation\Async\Guidelines\Examples\ForgetAwait;
async function speak(): Awaitable<void> {
echo "one";
await \HH\Asio\later();
echo "two";
echo "three";
}
async function forget_await(): Awaitable<void> {
$handle = speak(); // This just gets you the handle
}
forget_await();
Output
one
答案是未定義的。你可能會(huì)得到所有三個(gè)回音。你可能只得到第一個(gè)回音。你根本不會(huì)得到任何東西。保證speak()
完成的唯一方法就是完成await
。await
是Async調(diào)度程序的觸發(fā)器,允許HHVM適當(dāng)?shù)貢和:突謴?fù)speak()
; 否則,Async調(diào)度程序?qū)⒉惶峁┫嚓P(guān)的保證speak()
。
為了盡量減少任何不必要的副作用(例如排序差異),您的創(chuàng)建和等待等待應(yīng)盡可能接近。
<?hh
namespace Hack\UserDocumentation\Async\Guidelines\Examples\SideEffects;
async function get_curl_data(string $url): Awaitable<string> {
return await \HH\Asio\curl_exec($url);
}
function possible_side_effects(): int {
sleep(1);
echo "Output buffer stuff";
return 4;
}
async function proximity(): Awaitable<void> {
$handle = get_curl_data("http://example.com");
possible_side_effects();
await $handle; // instead you should await get_curl_data("....") here
}
\HH\Asio\join(proximity());
Output
Output buffer stuff
在上述示例中,possible_side_effects()當(dāng)您達(dá)到等待從網(wǎng)站獲取數(shù)據(jù)相關(guān)的句柄時(shí),可能會(huì)導(dǎo)致一些不期望的行為。
基本上,不依賴(lài)于同一代碼的運(yùn)行之間的輸出順序。即,不要編寫(xiě)Async代碼,其中排序很重要,而是通過(guò)等待和使用依賴(lài)關(guān)系await。
由于A(yíng)sync通常用于耗時(shí)的操作,因此記錄(即,緩存)Async調(diào)用的結(jié)果肯定是值得的。
該<<__Memoize>>屬性做正確的事。所以,如果可以,使用它。但是,如果你需要的記憶化的明確的控制,確保你memoize的的awaitable,而不是等待它的結(jié)果。
<?hh
namespace Hack\UserDocumentation\Async\Guidelines\Examples\MemoizeResult;
async function time_consuming(): Awaitable<string> {
sleep(5);
return "This really is not time consuming, but the sleep fakes it.";
}
async function memoize_result(): Awaitable<string> {
static $result = null;
if ($result === null) {
$result = await time_consuming(); // don't memoize the resulting data
}
return $result;
}
function runMe(): void {
$t1 = microtime();
\HH\Asio\join(memoize_result());
$t2 = microtime() - $t1;
$t3 = microtime();
\HH\Asio\join(memoize_result());
$t4 = microtime() - $t3;
var_dump($t4 < $t2); // The memmoized result will get here a lot faster
}
runMe();
Output
bool(true)
表面看來(lái),這似乎是合理的。我們要緩存與等待的相關(guān)的實(shí)際數(shù)據(jù)。然而,這可能會(huì)導(dǎo)致不良的競(jìng)爭(zhēng)條件。
試想一下,還有另外兩個(gè)Async函數(shù)等待的結(jié)果memoize_result(),稱(chēng)他們A()和B()。可能發(fā)生以下事件序列:
如果time_consuming()有副作用(例如數(shù)據(jù)庫(kù)寫(xiě)),那么這可能是一個(gè)嚴(yán)重的錯(cuò)誤。即使沒(méi)有副作用,它仍然是一個(gè)bug; 耗時(shí)的操作正在進(jìn)行多次,只需要完成一次。
相反,記住awaitable:
<?hh
namespace Hack\UserDocumentation\Async\Guidelines\Examples\MemoizeAwaitable;
async function time_consuming(): Awaitable<string> {
sleep(5);
return "Not really time consuming but sleep."; // For type-checking purposes
}
function memoize_handle(): Awaitable<string> {
static $handle = null;
if ($handle === null) {
$handle = time_consuming(); // memoize the awaitable
}
return $handle;
}
function runMe(): void {
$t1 = microtime();
\HH\Asio\join(memoize_handle());
$t2 = microtime() - $t1;
$t3 = microtime();
\HH\Asio\join(memoize_handle());
$t4 = microtime() - $t3;
var_dump($t4 < $t2); // The memmoized result will get here a lot faster
}
runMe();
Output
bool(true)
這簡(jiǎn)單地緩存句柄并逐字返回 - Async Vs Awaitable可以更詳細(xì)地解釋這一點(diǎn)。
如果它是緩存后等待句柄的Async函數(shù),這也將起作用。這可能看起來(lái)不直觀(guān),因?yàn)閍wait每次執(zhí)行該功能時(shí),即使在緩存命中路徑上也是如此。但是沒(méi)關(guān)系,除了第一個(gè)執(zhí)行之外的每個(gè)執(zhí)行$handle都不行null,所以一個(gè)新的實(shí)例time_consuming()不會(huì)被啟動(dòng)。一個(gè)現(xiàn)有實(shí)例的結(jié)果將被共享。
任何一種方法都有效,但非Async緩存包裝可以更容易理解。
Lambdas可以減少編寫(xiě)完整關(guān)閉語(yǔ)法的代碼冗長(zhǎng)度。它們與Async實(shí)用工具協(xié)同工作非常有用。
例如,可以使用lambdas來(lái)縮短以下三種方式來(lái)完成相同的事情。
<?hh
namespace Hack\UserDocumentation\Async\Guidelines\Examples\Lambdas;
// For asio-utilities that we installed via composer
async function fourth_root(num $n): Awaitable<float> {
return sqrt(sqrt($n));
}
async function normal_call(): Awaitable<Vector<float>> {
$nums = Vector {64, 81};
return await \HH\Asio\vm(
$nums,
fun('\Hack\UserDocumentation\Async\Guidelines\Examples\Lambdas\fourth_root')
);
}
async function closure_call(): Awaitable<Vector<float>> {
$nums = Vector {64, 81};
$froots = async function(num $n): Awaitable<float> {
return sqrt(sqrt($n));
};
return await \HH\Asio\vm($nums, $froots);
}
async function lambda_call(): Awaitable<Vector<float>> {
$nums = Vector {64, 81};
return await \HH\Asio\vm($nums, async $num ==> sqrt(sqrt($num)));
}
async function use_lambdas(): Awaitable<void> {
$nc = await normal_call();
$cc = await closure_call();
$lc = await lambda_call();
var_dump($nc);
var_dump($cc);
var_dump($lc);
}
\HH\Asio\join(use_lambdas());
Output
object(HH\Vector)#8 (2) {
[0]=>
float(2.8284271247462)
[1]=>
float(3)
}
object(HH\Vector)#16 (2) {
[0]=>
float(2.8284271247462)
[1]=>
float(3)
}
object(HH\Vector)#24 (2) {
[0]=>
float(2.8284271247462)
[1]=>
float(3)
}
想象一下,您正在從非同步范圍調(diào)用async函數(shù)join_async()。為了獲得您期望的結(jié)果,您必須join()為了獲得等待的結(jié)果。
<?hh
namespace Hack\UserDocumentation\Async\Guidelines\Examples\Join;
async function join_async(): Awaitable<string> {
return "Hello";
}
// In an async function, you would await an awaitable.
// In a non-async function, or the global scope, you can
// use `join` to force the the awaitable to run to its completion.
$s = \HH\Asio\join(join_async());
var_dump($s);
Output
string(5) "Hello"
這種情況通常發(fā)生在全局范圍內(nèi)(但可能發(fā)生在任何地方)。
Async功能不在同一時(shí)間運(yùn)行。它們是通過(guò)在執(zhí)行代碼中等待狀態(tài)的改變(即搶占式多任務(wù))來(lái)進(jìn)行CPU共享。Async還存在于正常PHP和Hack的單線(xiàn)程世界中。
你可以await在三個(gè)地方使用:
你不能,例如,用await在var_dump()。
更多建議: