SyscallContext

按照nikic的思路引入与调度器内部交互的Syscall,将需要执行的函数打包成Syscall,通过yield返回迭代器,可以从Syscall参数获取到当前迭代器对象,这里提供了一个外界与AsyncTask交互的扩展点。

我们借此演示如何添加跨生成器上下文,在嵌套生成器共享数据,解耦生成器之间依赖。

  1. <?php
  2. final class AsyncTask implements Async
  3. {
  4. public $gen;
  5. public $continuation;
  6. public $parent;
  7. // 我们在构造器添加$parent参数, 把父子生成器链接起来,使其可以进行回溯
  8. public function __construct(\Generator $gen, AsyncTask $parent = null)
  9. {
  10. $this->gen = new Gen($gen);
  11. $this->parent = $parent;
  12. }
  13. public function begin(callable $continuation)
  14. {
  15. $this->continuation = $continuation;
  16. $this->next();
  17. }
  18. public function next($result = null, $ex = null)
  19. {
  20. try {
  21. if ($ex) {
  22. $value = $this->gen->throw_($ex);
  23. } else {
  24. $value = $this->gen->send($result);
  25. }
  26. if ($this->gen->valid()) {
  27. // 这里注意优先级, Syscall 可能返回\Generator 或者 Async
  28. if ($value instanceof Syscall) { // Syscall 签名见下方
  29. $value = $value($this);
  30. }
  31. if ($value instanceof \Generator) {
  32. $value = new self($value, $this);
  33. }
  34. if ($value instanceof Async) {
  35. $cc = [$this, "next"];
  36. $value->begin($cc);
  37. } else {
  38. $this->next($value, null);
  39. }
  40. } else {
  41. $cc = $this->continuation;
  42. $cc($result, null);
  43. }
  44. } catch (\Exception $ex) {
  45. if ($this->gen->valid()) {
  46. $this->next(null, $ex);
  47. } else {
  48. $cc = $this->continuation;
  49. $cc($result, $ex);
  50. }
  51. }
  52. }
  53. }

Syscall将 (callable :: AsyncTask $task -> mixed) 包装成单独类型:

  1. <?php
  2. class Syscall
  3. {
  4. private $fun;
  5. public function __construct(callable $fun)
  6. {
  7. $this->fun = $fun;
  8. }
  9. public function __invoke(AsyncTask $task)
  10. {
  11. $cb = $this->fun;
  12. return $cb($task);
  13. }
  14. }

因为PHP对象属性为Hashtable实现,而生成器对象本身无任何属性,我们这里把Context的KV数据附加到根生成器对象上,然后得到的Context的Get与Set函数:

  1. <?php
  2. function getCtx($key, $default = null)
  3. {
  4. return new Syscall(function(AsyncTask $task) use($key, $default) {
  5. while($task->parent && $task = $task->parent);
  6. if (isset($task->gen->generator->$key)) {
  7. return $task->gen->generator->$key;
  8. } else {
  9. return $default;
  10. }
  11. });
  12. }
  13. function setCtx($key, $val)
  14. {
  15. return new Syscall(function(AsyncTask $task) use($key, $val) {
  16. while($task->parent && $task = $task->parent);
  17. $task->gen->generator->$key = $val;
  18. });
  19. }
  1. <?php
  2. function setTask()
  3. {
  4. yield setCtx("foo", "bar");
  5. }
  6. function ctxTest()
  7. {
  8. yield setTask();
  9. $foo = (yield getCtx("foo"));
  10. echo $foo;
  11. }
  12. $task = new AsyncTask(ctxTest());
  13. $task->begin($trace); // output: bar