Async扩展
Async本身是一个非常有用的构造,它将通过其合作多任务基础设施提供可能的节省时间。然而,我们知道将有一组常用的功能,其中Async最有用:数据库访问和缓存,Web资源访问和流。
MySQL的
Async MySQL扩展类似于 mysqliHHVM附带的扩展。此扩展将主要用于Async创建连接和查询MySQL数据库。
在完整的API将包含所有可以通过Async访问MySQL中的类和方法; 我们将在这里介绍一些更常见的情况。
连接到MySQL数据库的主要类别是 AsyncMysqlConnectionPool其主要方法是异Async connect()。
查询数据库的主要类别是 AsyncMysqlConnection使用两种主要的查询方法,query()以及queryf()两种Async。还有一个功能来确保要执行的查询被安全地调用escapeString()。
从查询中检索结果的主类是一个被称为抽象类AsyncMysqlResult,它本身有两个被称为的具体子类AsyncMysqlQueryResult 和 AsyncMysqlErrorResult。这些类的主要方法是,vectorRows()并且mapRows()都是非同步的。
<?hh
class AsyncMysqlConnectionPool {
public function __construct(array $pool_options): void;
public static async function connect(
string $host,
int $port,
string $dbname,
string $user,
string $password,
int $timeout_micros = -1,
string $extra_key = ""): Awaitable<AsyncMysqlConnection>;
// More methods in this class, of course
}
class AsyncMysqlConnection {
public function query(string $query, int $timeout_micros)
: Awaitable<AsyncMysqlResult>;
public function queryf(string $pattern, ..$args)
: Awaitable<AsyncMysqlResult>;
public function escapeString(string $data): string;
// More methods in this class, of course
}
class AsyncMysqlQueryResult extends AsyncMysqlResult {
public function vectorRows(): Vector; // vector of Vectors
public function mapRows(): Vector; // vector of Maps
// return db column values as Hack types instead of
// string representations of those types.
public function vectorRowsTyped(): Vector;
public function mapRowsTyped(): Vector;
// More methods in this class, of course
}
class AsyncMysqlErrorResult extends AsyncMysqlResult {
public function failureType(): string;
public function mysql_errno(): int;
public function mysql_error(): string;
// More methods in this class, of course
}
以下是一个简单的示例,显示如何从具有此扩展名的数据库获取用户名。
<?hh
namespace Hack\UserDocumentation\Async\Extensions\Examples\MySQL;
use \Hack\UserDocumentation\Async\Extensions\Examples\AsyncMysql\ConnectionInfo as CI;
async function get_connection(): Awaitable<\AsyncMysqlConnection> {
// Get a connection pool with default options
$pool = new \AsyncMysqlConnectionPool(array());
// Change credentials to something that works in order to test this code
return await $pool->connect(
CI::$host,
CI::$port,
CI::$db,
CI::$user,
CI::$passwd,
);
}
async function fetch_user_name(\AsyncMysqlConnection $conn,
int $user_id) : Awaitable<string> {
// Your table and column may differ, of course
$result = await $conn->queryf(
'SELECT name from test_table WHERE userID = %d',
$user_id
);
// There shouldn't be more than one row returned for one user id
invariant($result->numRows() === 1, 'one row exactly');
// A vector of vector objects holding the string values of each column
// in the query
$vector = $result->vectorRows();
return $vector[0][0]; // We had one column in our query
}
async function get_user_info(\AsyncMysqlConnection $conn,
string $user): Awaitable<Vector<Map>> {
$result = await $conn->queryf(
'SELECT * from test_table WHERE name = %s',
$conn->escapeString($user)
);
// A vector of map objects holding the string values of each column
// in the query, and the keys being the column names
$map = $result->mapRows();
return $map;
}
async function async_mysql_tutorial(): Awaitable<void> {
$conn = await get_connection();
if ($conn !== null) {
$result = await fetch_user_name($conn, 2);
var_dump($result);
$info = await get_user_info($conn, 'Fred Emmott');
var_dump($info instanceof Vector);
var_dump($info[0] instanceof Map);
}
}
\HH\Asio\join(async_mysql_tutorial());
Output
string(11) "Fred Emmott"
bool(true)
bool(true)
连接池
Async MySQL扩展不支持复用 - 每个并发查询需要自己的连接。但是,扩展支持连接池。
Async MySQL扩展提供了一种池连接对象的机制,因此您不需要在每次进行查询时创建新的连接。the class是AsyncMysqlConnectionPool 一个可以这样创建:
?hh
namespace Hack\UserDocumentation\Async\Extensions\Examples\MySQLConnectionPool;
use \Hack\UserDocumentation\Async\Extensions\Examples\AsyncMysql\ConnectionInfo as CI;
function get_pool(): \AsyncMysqlConnectionPool {
return new \AsyncMysqlConnectionPool(
array('pool_connection_limit' => 100)
); // See API for more pool options
}
async function get_connection(): Awaitable<\AsyncMysqlConnection> {
$pool = get_pool();
$conn = await $pool->connect(
CI::$host,
CI::$port,
CI::$db,
CI::$user,
CI::$passwd,
);
return $conn;
}
async function run(): Awaitable<void> {
$conn = await get_connection();
var_dump($conn);
}
\HH\Asio\join(run());
Output
object(AsyncMysqlConnection)#6 (0) {
}
这是强烈建议您使用连接池为您的MySQL连接; 如果由于某种原因你真的需要一个,单个异步客户端,有一个AsyncMysqlClient
提供一个connect()
方法的类。
MCRouter
MCRouter是一个memcached协议路由库。为了帮助您的memcached memcached部署,它提供了连接池,基于前缀的路由等功能。
Async MCRouter扩展基本上是作为HHVM一部分的Memcached扩展的Async但是子集的版本。主要课程是MCRouter。有两种方法可以创建MCRouter对象的实例。该createSimple()采取其中Memcached是正在运行的服务器地址的矢量。更多的配置__construct()允许更多的选项调整。得到一个对象后,您可以使用Async的核心memcached协议的方法,如版本add(),get()和del()。
class MCRouter {
public function __construct(array<stirng, mixed> $options, string $pid = '');
public static function createSimple(ConstVector<string> $servers): MCRouter;
public async function add(string $key, string $value, int $flags = 0,
int $expiration = 0): Awaitable<void>;
public async function get(string $key): Awaitable<string>;
public async function del(string $key): Awaitable<void>;
// More methods exist, of course
}
以下是一个简单的示例,显示如何从memcached获取用户名:
<?hh
namespace Hack\UserDocumentation\Async\Extensions\Examples\MCRouter;
require __DIR__ . "/../../../../vendor/hh_autoload.php"; // For wrap()
function get_mcrouter_object(): \MCRouter {
$servers = Vector { getenv('HHVM_TEST_MCROUTER') };
$mc = \MCRouter::createSimple($servers);
return $mc;
}
async function add_user_name(
\MCRouter $mcr,
int $id,
string $value): Awaitable<void> {
$key = 'name:' . $id;
await $mcr->set($key, $value);
}
async function get_user_name(\MCRouter $mcr, int $user_id): Awaitable<string> {
$key = 'name:' . $user_id;
try {
$res = await \HH\Asio\wrap($mcr->get($key));
if ($res->isSucceeded()) {
return $res->getResult();
}
return "";
} catch (\MCRouterException $ex) {
echo $ex->getKey() . PHP_EOL . $ex->getOp();
return "";
}
}
async function run(): Awaitable<void> {
$mcr = get_mcrouter_object();
await add_user_name($mcr, 1, 'Joel');
$name = await get_user_name($mcr, 1);
var_dump($name); // Should print "Joel"
}
\HH\Asio\join(run());
Output
string(4) "Joel"
如果使用此协议时出现问题,则可能会抛出两个可能的异常。MCRouterException
当核心选项发生错误时,如删除密钥一样被抛出。MCRouterOptionException
当您提供不可解析的选项列表时发生。
卷曲
Hack目前为cURL提供了两个Async功能。
curl_multi_await
cURL为URL提供了一个数据传输库。异步cURL扩展提供了两个功能,其中一个是围绕另一个的包装。curl_multi_await()是HHVM的异步版本curl_multi_select()。它等待直到cURL句柄上有活动,并且完成后,您可以使用curl_multi_exec()处理结果,就像在non-async情况下一样。
async function curl_multi_await(resource $mh,
float $timeout = 1.0): Awaitable<int>;
curl_exec
HH\Asio\curl_exec()是一个包装纸curl_multi_await()。它很容易使用,因为您不必担心资源创建,因为您只需传递字符串URL即可。
namespace HH\Asio {
async function curl_exec(mixed $urlOrHandle): Awaitable<string>;
}
以下是使用lambda来获取URL内容向量的示例,以减少使用完全关闭语法的代码冗长度。
<?hh
namespace Hack\UserDocumentation\Async\Extensions\Examples\Curl;
function get_urls(): Vector<string> {
return Vector {
"http://example.com",
"http://example.net",
"http://example.org",
};
}
async function get_combined_contents(Vector $urls): Awaitable<Vector<string>> {
// Use lambda shorthand syntax here instead of full closure syntax
$handles = $urls->mapWithKey(($idx, $url) ==> \HH\Asio\curl_exec($url));
$contents = await \HH\Asio\v($handles);
echo $contents->count();
return $contents;
}
\HH\Asio\join(get_combined_contents(get_urls()));
Output
3
Streams
Async stream扩展有一个功能, stream_await()
,其功能类似于HHVM stream_select()
。它等待流进入状态(例如STREAM_AWAIT_READY
),但没有多路复用功能stream_select()
。您可以使用HH \ Asio \ v()等待多个流句柄,但是所有组合等待的结果将不会完成,直到所有底层流完成。
async function stream_await(resource $fp, int $events,
float $timeout = 0.0): Awaitable<int>;
此示例显示如何使用 stream_await() 写资源。
<?hh
namespace Hack\UserDocumentation\Async\Extensions\Examples\Stream;
function get_resources(): array<resource> {
$r1 = fopen('php://stdout', 'w');
$r2 = fopen('php://stdout', 'w');
$r3 = fopen('php://stdout', 'w');
return array($r1, $r2, $r3);
}
async function write_all(array<resource> $resources): Awaitable<void> {
// UNSAFE : the typechecker isn't aware of stream_await until 3.12 :(
$write_single_resource = async function(resource $r) {
$status = await stream_await($r, STREAM_AWAIT_WRITE, 1.0);
if ($status === STREAM_AWAIT_READY) {
fwrite($r, str_shuffle('ABCDEF') . PHP_EOL);
}
};
// You will get 3 shuffled strings, each on a separate line.
await \HH\Asio\v(array_map($write_single_resource, $resources));
}
\HH\Asio\join(write_all(get_resources()));
Output
DCFAEB
BAFCDE
ACBDEF