hprose php用户手册,04 协程 - 《Hprose for PHP 用户手册》 - 书栈网 · BookStack

基本用法

PHP 5.5 引入了 Generator,Generator 通过封装之后,可以作为协程来进行使用。

Hprose 也提供了对 Generator 的一个封装,并且跟 Promise 相结合之后,可以实现异步代码同步化。

让我们来看一个例子:use\Hprose\Future;

use\Hprose\Http\Client;

Future\co(function(){

$test=newClient("http://hprose.com/example/");

var_dump((yield$test->hello("hprose")));

$a=$test->sum(1,2,3);

$b=$test->sum(4,5,6);

$c=$test->sum(7,8,9);

var_dump((yield$test->sum($a,$b,$c)));

var_dump((yield$test->hello("world")));

});

Future\co(也可以用 Promise\co)就是一个协程封装函数。它的功能以协程的方式来执行生成器函数。该方法允许带入参数执行。

在上面的例子中,$test 是一个 Hprose 的异步 Http 客户端。Hprose 2.0 的默认客户端为异步客户端,而不是同步客户端,这一点与 1.x 不同。

所以 $test->hello 和 $test->sum 两个调用的返回值实际上是一个 promise 对象。而 yield 关键字在这里的作用就是,可以等待调用完成并返回 promise 所包含的值,如果 promise 的最后的状态为 REJECTED,那么 yield 将抛出一个异常,异常的值为 promise 对象中的 reason 属性值。

在上面的调用中,$a, $b, $c 三个变量都是 promise 对象,而 $test->sum 可以直接接受 promise 参数作为调用参数,当 $a, $b, $c 三个 promise 对象的状态都变为 FULFILLED 状态时,$test-sum($a, $b, $c) 才会真正的开始调用。而获取 $a,$b,$c 的三个调用是异步并发执行的。

上面程序的执行结果为:string(12)"Hello hprose"

int(45)

string(11)"Hello world"

从结果我们可以看出,co 函数和 yield 的结合可以很方便的让异步程序编写同步化。这也是 Hprose 2.0 最有特色的改进之一。

虽然上面用 Hprose 远程调用来举例,但是 co 函数所实现的协程不是只对 Hprose 远程调用有效,而是对任何返回 promise 的对象都有效。所以,即使你不使用 Hprose 远程调用,也可以使用 co 函数和 Promise 来进行异步代码的同步化编写。

多协程并发

我们前面说过,如果在同一个协程内进行远程调用,如果不加 yield 关键字,多个远程调用就是并发执行的。加上 yield 关键字,就会变成顺序执行。

那么当开两个或多个协程时,结果是什么样子呢?我们来看一个例子:use\Hprose\Future;

use\Hprose\Http\Client;

$test=newClient("http://hprose.com/example/");

Future\co(function()use($test){

for($i=0;$i<5;$i++){

var_dump((yield$test->hello("1-".$i)));

}

});

Future\co(function()use($test){

for($i=0;$i<5;$i++){

var_dump((yield$test->hello("2-".$i)));

}

});

我们运行该程序之后,可以看到如下结果:string(9)"Hello 2-0"

string(9)"Hello 1-0"

string(9)"Hello 1-1"

string(9)"Hello 2-1"

string(9)"Hello 2-2"

string(9)"Hello 1-2"

string(9)"Hello 1-3"

string(9)"Hello 2-3"

string(9)"Hello 2-4"

string(9)"Hello 1-4"

这个运行结果并不唯一,我们有可能看到不同顺序的输出,但是有一点可以保证,就是 Hello-1-X 中的 X 是按照顺序输出的,而 Hello-2-Y 中的 Y 也是按照顺序输出的。

也就是说,每个协程内的语句是按照顺序执行的,而两个协程确是并行执行的。

不过有一点要注意,上面的例子跟第一个例子有一点不同,那就是我们把 $test 客户端的创建拿到了协程外面。

如果像第一个例子那样放在协程中,我们就看不到这样的并发执行结果了。原因是,这里的每个客户端都有一个自己独立的事件循环,只有当一个事件循环执行完之后,才会执行另一个事件循环。

但如果换成 Hprose 的 Swoole 的客户端,则不管是放在里面还是外面,看到的都是并发执行的结果,原因是 Swoole 客户端的事件循环是统一的。而且 Swoole 的事件循环一旦开始,如果不手动执行 swoole_event_exit,事件循环不会结束,你也就不会看到程序退出。在使用 Swoole 客户端时,需要注意这一点。

协程的参数和返回值

co 函数允许传参给协程。

而且 co 函数本身的返回值也是一个 promise 对象。它的值在 PHP 5 和 PHP 7 中略有差别。

PHP 5 的生成器函数本身不允许使用 return 返回值。例如:functiontest(){

$x=(yield1);

return$x;

}

这样的代码是非法的(但你不用担心因为写了这样的代码而被抓去坐牢

68747470733a2f2f6173736574732d63646e2e6769746875622e636f6d2f696d616765732f69636f6e732f656d6f6a692f756e69636f64652f31663630362e706e67">)。但是在 PHP 7 中,这种写法是被允许的,但这个返回值跟普通函数的返回值是不同的。PHP 7 中为 Generator 对象提供了一个 getReturn 方法来专门获取这个返回值。

co 函数的返回值在 PHP 5 中是最后一次执行的 yield 的返回值的 promise 包装。在 PHP 7 中,如果没有 return 语句,或者 return 语句没有返回值(或者返回值为 NULL),那么,co 函数的返回值跟 PHP 5 相同。如果在 PHP 7 中,使用了 return 语句并且有返回值,比如像上面那个 test 函数,那么返回值为 return 语句返回值的 promise 包装。

因此如果你的代码是按照 PHP 5 的方式编写的,那么执行的效果在 PHP 5 和 PHP 7 中是相同的,如果你是按照 PHP 7 的方式单独编写的,那么你也能够得到你希望得到的 PHP 7 的返回值。因此,co 函数既做到了对旧版本的兼容性,又做到了对新版本特殊功能的支持。

因为 co 函数的结果本身也是一个 promise 对象,因此,你也可以在另外一个协程中来 yieldco 函数的执行结果。

下面这个例子演示了传参和 co 函数返回值的使用:use\Hprose\Future;

use\Hprose\Http\Client;

$test=newClient("http://hprose.com/example/");

functionhello($n,$test){

$result=array();

for($i=0;$i<5;$i++){

$result[]=$test->hello("$n-$i");

}

yieldFuture\all($result);

}

Future\co(function()use($test){

$result=(yieldFuture\co(function($test){

$result=array();

for($i=0;$i<3;$i++){

$result[]=Future\co('hello',$i,$test);

}

yieldFuture\all($result);

},$test));

var_dump($result);

});

该程序执行结果为:array(3){

[0]=>

array(5){

[0]=>

string(9)"Hello 0-0"

[1]=>

string(9)"Hello 0-1"

[2]=>

string(9)"Hello 0-2"

[3]=>

string(9)"Hello 0-3"

[4]=>

string(9)"Hello 0-4"

}

[1]=>

array(5){

[0]=>

string(9)"Hello 1-0"

[1]=>

string(9)"Hello 1-1"

[2]=>

string(9)"Hello 1-2"

[3]=>

string(9)"Hello 1-3"

[4]=>

string(9)"Hello 1-4"

}

[2]=>

array(5){

[0]=>

string(9)"Hello 2-0"

[1]=>

string(9)"Hello 2-1"

[2]=>

string(9)"Hello 2-2"

[3]=>

string(9)"Hello 2-3"

[4]=>

string(9)"Hello 2-4"

}

}

在这个程序里,所有的调用都是并发执行的,最后一次 yield 汇集最终所有结果。yield 语句在这里同时扮演了 return 的角色。

wrap 包装函数和 yield 的区别

我们在 Promise 异步编程 一章中,介绍了功能强大的 wrap 函数。通过它包装的函数可以直接将 promise 对象像普通参数一样带入函数执行。但是要注意,wrap 包装之后的函数虽然看上去像是同步的,但是实际上是异步执行的。当你有多个 wrap 包装的函数顺序执行的时候,实际上并不保证执行顺序按照书写顺序来。而 yield 则是同步的,它一定会保证 yield 语句的执行顺序。

我们来看一个例子:use\Hprose\Future;

use\Hprose\Http\Client;

$test=newClient("http://hprose.com/example/");

Future\co(function()use($test){

for($i=0;$i<5;$i++){

var_dump((yield$test->hello("1-".$i)));

}

$var_dump=Future\wrap('var_dump');

for($i=0;$i<5;$i++){

$var_dump($test->hello("2-".$i));

}

for($i=0;$i<5;$i++){

var_dump((yield$test->hello("3-".$i)));

}

});

运行该程序之后,执行结果为:string(9)"Hello 1-0"

string(9)"Hello 1-1"

string(9)"Hello 1-2"

string(9)"Hello 1-3"

string(9)"Hello 1-4"

string(9)"Hello 2-0"

string(9)"Hello 2-2"

string(9)"Hello 3-0"

string(9)"Hello 2-1"

string(9)"Hello 2-3"

string(9)"Hello 2-4"

string(9)"Hello 3-1"

string(9)"Hello 3-2"

string(9)"Hello 3-3"

string(9)"Hello 3-4"

这个结果可能每次执行都不一样。

但是,Hello 1-X 始终都是按照顺序输出的,而且始终都是在 Hello 2-Y 和 Hello 3-Z 之前输出的。

Hello 2-Y 的输出则不是按照顺序输出的(虽然偶尔结果也是按照顺序输出,但这一点并不能保证),而且它甚至还会穿插在 Hello 3-Z 的输出结果中。

Hello 3-Z 本身也是按照顺序输出的,但是 Hello 2-Y 却可能穿插在它的输出中间,原因是 Hello 2-Y 先执行,并且是异步执行的,因此它并不等结果执行完,就开始执行后面的语句了,所以当它执行完时,可能已经执行过几条 Hello 3-Z 的 yield 语句了。

将协程包装成闭包函数

wrap 函数不仅仅可以将普通函数包装成支持 promise 参数的函数。

wrap 函数还支持将协程包装成闭包函数的功能,包装之后的函数,不仅可以将协程当做普通函数一样执行,而且还支持传递 promise 参数。例如:use\Hprose\Future;

use\Hprose\Http\Client;

$test=newClient("http://hprose.com/example/");

$coroutine=Future\wrap(function($test){

var_dump(1);

var_dump((yield$test->hello("hprose")));

$a=$test->sum(1,2,3);

$b=$test->sum(4,5,6);

$c=$test->sum(7,8,9);

var_dump((yield$test->sum($a,$b,$c)));

var_dump((yield$test->hello("world")));

});

$coroutine($test);

$coroutine(Future\value($test));

该程序执行结果为:int(1)

int(1)

string(12)"Hello hprose"

string(12)"Hello hprose"

int(45)

int(45)

string(11)"Hello world"

string(11)"Hello world"

我们会发现通过 wrap 函数包装的协程,不再需要使用 co 函数来执行了。

另外,wrap 函数包装的对象上的生成器方法也会自动变为普通方法。例如:use\Hprose\Future;

classTest{

functiontestco($x){

yield$x;

}

}

$test=Future\wrap(newTest());

$test->testco(123)->then('var_dump');

$test->testco(Future\value('hello'))->then('var_dump');

该程序运行结果为:int(123)

string(5)"hello"

协程与异常处理

在协程内,yield 不但可以将异步的 promise 结果转换成同步结果,而且可以将 REJECTED 状态的 promise 对象转换为抛出异常。例如:useHprose\Client;

useHprose\Future;

Future\co(function(){

$client=Client::create('http://hprose.com/example/');

try{

(yield$client->ooxx());

}

catch(Exception$e){

echo$e->getMessage();

}

});

该程序运行结果为:Can't find this function ooxx().

在协程内抛出的异常如果没有用 trycatch 语句捕获,那么第一个抛出的异常将会中断协程的执行,并将整个协程的返回值设置为 REJECTED 状态的 promise 对象,异常本身作为 reason 的值。例如:useHprose\Client;

useHprose\Future;

Future\co(function(){

$client=Client::create('http://hprose.com/example/');

(yield$client->oo());

(yield$client->xx());

})->catchError(function($e){

echo$e->getMessage();

});

该程序运行结果为:Can't find this function oo().

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,你的问题是关于 Hprose for Java 服务器的吗?那么,让我来为你解答。Hprose是一种跨语言的RPC协议,提供了多种语言的实现方式,包括Java、PHP、Python、JavaScript等。在Java中,我们可以使用Hprose for Java来实现服务器端的开发。 要使用Hprose for Java,首先需要在项目中引入相应的依赖。在Maven项目中,可以在pom.xml文件中添加以下依赖: ``` <dependency> <groupId>com.github.hsiafan</groupId> <artifactId>hprose-java</artifactId> <version>3.0.8</version> </dependency> ``` 在使用Hprose for Java时,需要定义一个接口,并在接口上添加@HproseService注解,表示这是一个Hprose服务。接着,我们可以在接口中定义需要暴露给客户端调用的方法,方法的参数和返回值可以是任意类型。例如: ``` @HproseService public interface MyService { String sayHello(String name); } ``` 在实现类中,我们需要实现接口中定义的方法,并启动Hprose服务器,将实现类注册为服务提供者。例如: ``` public class MyServiceImpl implements MyService { @Override public String sayHello(String name) { return "Hello, " + name + "!"; } public static void main(String[] args) throws IOException { MyService service = new MyServiceImpl(); HproseTcpServer server = new HproseTcpServer("tcp://0.0.0.0:8080"); server.add(service, MyService.class); server.start(); } } ``` 以上代码创建了一个TCP协议的Hprose服务器,监听8080端口,将MyServiceImpl实例注册为服务提供者,并暴露了一个名为sayHello的方法。 客户端可以使用Hprose for Java提供的客户端类来调用服务端的方法。例如: ``` public class MyClient { public static void main(String[] args) throws IOException { HproseTcpClient client = new HproseTcpClient("tcp://localhost:8080"); MyService service = client.useService(MyService.class); String result = service.sayHello("world"); System.out.println(result); } } ``` 以上代码创建了一个TCP协议的Hprose客户端,连接到本地的8080端口,并调用了服务端的sayHello方法。 这就是Hprose for Java服务器端的基本使用方法。希望能够对你有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值