资讯专栏INFORMATION COLUMN

PHP下的异步尝试三:协程的PHP版thunkify自动执行器

wudengzan / 264人阅读

摘要:函数并不是生成器协程函数自动执行的唯一方案。因为自动执行的关键是,必须有一种机制,自动控制生成器协程函数的流程,接收和交还程序的执行权。回调函数可以做到这一点,对象也可以做到这一点。本系列的下一篇,将介绍基于的实现的自动执行器。

PHP下的异步尝试系列

如果你还不太了解PHP下的生成器和协程,你可以根据下面目录翻阅

PHP下的异步尝试一:初识生成器

PHP下的异步尝试二:初识协程

PHP下的异步尝试三:协程的PHP版thunkify自动执行器

PHP下的异步尝试四:PHP版的Promise

[PHP下的异步尝试五:PHP版的Promise的继续完善]

高阶函数

在我们实现自动调度(器)函数前,我们先来理解下高阶函数

thunk函数
# 先求值再传参
function func(m){
  return m * 2;     
}

f(x + 5);

// 等同于

# 先传参再求值
var thunk = function () {
  return x + 5;
};

function func(thunk){
  return thunk() * 2;
}

# 这段我们在python或一些语言里,概念叫高阶函数
# 因为php是解释性动态语言,所以函数可以当参数传入
# 这里python,js,php下函数都是可以传参的
PHP版本的thunkify函数

thunkify实现原理:

包装一次原始函数名,然后返回一个第一次匿名函数(并携带包装函数): return function () use ($func){$args = func_get_args();}

然后再获取该匿名函数的参数,并在上一次第一次匿名函数体内返回一次带回调参数的第二次匿名函数(并携带上一次环境上下文): return function ($callback) use ($args, $func){}

调用包装函数,参数为:第一次匿名函数调用的参数+一个回调函数

function thunkify($func){
    return function () use ($func) {
        $args = func_get_args();
        return function ($callback) use ($args, $func) {
            array_push($args, $callback);
            return $func(...$args);
        };
  };
};

$printStr = function($p1, $p2, $callback) {
    $callback($p1, $p2);
};

$printStrThunkify = thunkify($printStr);

$printStrThunkify(...["foo", "bar"])(function (...$p) {
    var_dump($p);
});

# output
array(2) {
  [0]=>
  string(3) "foo"
  [1]=>
  string(3) "bar"
}
只能执行一次回调的thunkify函数
function thunkify($func){
    return function () use ($func) {
        $args = func_get_args();
        return function ($callback) use ($args, $func) {
            // 原本的获取参数,回调会多次执行
            // array_push($args, $callback); 
            // 增加回调只能执行一次
            $callbackCalled = false;
            array_push($args, function (...$params) use ($callback, &$callbackCalled) {
                if ($callbackCalled) return ;
                $callbackCalled = true;
                $callback(...$params);
            });
            return $func(...$args);
        };
    };
};

$printStr = function($p1, $p2, $callback) {
    $callback($p1, $p2);
    $callback($p1, $p2); //我们增加一次回调
};

$printStrThunkify = thunkify($printStr);

$printStrThunkify(...["foo", "bar"])(function (...$p) {
    var_dump($p);
});

# output
array(2) {
  [0]=>
  string(3) "foo"
  [1]=>
  string(3) "bar"
}

看到这里,你可能还在疑惑,thunkify函数其实只是帮我们包装了一次有回调函数的高阶函数而已
不过这里到底有什么用处呢,在普通场景下确实用户不大(可能用处单纯就在做一些前后置函数包装也是用处的,类似python的装饰)
但是,但是,但是在生成器协程里,Thunkify函数可以用于生成器协程的自动流程管理。

生成器协程的自动执行基础理解

每一次yield出来的结果都是一个thunk函数的回调

function thunkify($func){
    return function () use ($func) {
        $args = func_get_args();
        return function ($callback) use ($args, $func) {
            $callbackCalled = false;
            array_push($args, function (...$params) use ($callback, &$callbackCalled) {
                if ($callbackCalled) return ;
                $callbackCalled = true;
                $callback(...$params);
            });
            return $func(...$args);
        };
    };
};

$printStr1 = function($p1, $callback) {
    $callback($p1);
};
$printStr2 = function($p1, $callback) {
    $callback($p1);
};

$printStrThunkify1 = thunkify($printStr1);
$printStrThunkify2 = thunkify($printStr2);

function gen()
{
    global $printStrThunkify1, $printStrThunkify2;

    $r1 = yield $printStrThunkify1("1");
    var_dump($r1);
    $r2 = yield $printStrThunkify2("2");
    var_dump($r2);
}

$gen = gen();

// 手动回调, 模拟自动执行基础理解
$value = $gen->current();
$value(function ($p1) use($gen) {
    $value = $gen->send($p1);
    $value(function ($p1) use($gen) {
        $value = $gen->send($p1);
        var_dump($value);
    });
});
自动执行器

我们这里只是实现上面的手动回调执行
增加了一个自动执行器,把生成器协程传入后讲自动执行生成器协程

function thunkify($func){
    return function () use ($func) {
        $args = func_get_args();
        return function ($callback) use ($args, $func) {
            $callbackCalled = false;
            array_push($args, function (...$params) use ($callback, &$callbackCalled) {
                if ($callbackCalled) return ;
                $callbackCalled = true;
                $callback(...$params);
            });
            return $func(...$args);
        };
    };
};

$printStr1 = function($p1, $callback) {
    sleep(2);
    $callback($p1);
};
$printStr2 = function($p1, $callback) {
    sleep(5);
    $callback($p1);
};

$printStrThunkify1 = thunkify($printStr1);
$printStrThunkify2 = thunkify($printStr2);

function gen()
{
    global $printStrThunkify1, $printStrThunkify2;

    $r1 = yield $printStrThunkify1("1");
    var_dump($r1);
    $r2 = yield $printStrThunkify2("2");
    var_dump($r2);
}

function autoCaller(Generator $gen)
{
    // 注意这里的$next use 引入作用域必须带上&, 否则无法识别
    $next = function ($p1) use ($gen, &$next) {

        if (is_null($p1)) { //此处获取第一次yeild的回调
            $result = $gen->current();
        } else {
            // send后返回的是下一次的yield值
            $result = $gen->send($p1);
        }

        // 是否生成器迭代完成
        // 迭代器生成完成,不再迭代执行(自动执行器返回停止)
        if (!$gen->valid()) {
            return ;
        }

        $result($next);
    };

    $next(null);
}

$gen1 = gen();
//$gen2 = gen();

autoCaller($gen1);
//autoCaller($gen2);

# output
string(1) "1"
string(1) "2"

# 如果我们打开上面的两个sleep()注释
# output

# 等待2秒
string(1) "1"
# 等待5秒
string(1) "2"

# 因为这里我们的thunk里执行的实际函数是同步的代码,所以整体是阻塞的后续代码执行的
总结

只要执行 autoCaller 函数,生成器就会自动迭代完成。这样一来,异步操作不仅可以写得像同步操作,而且一行代码就可以执行。

Thunkify函数并不是 生成器协程 函数自动执行的唯一方案。

因为自动执行的关键是,必须有一种机制,自动控制 生成器协程 函数的流程,接收和交还程序的执行权。

回调函数可以做到这一点,Promise 对象也可以做到这一点。本系列的下一篇,将介绍基于PHP的Promise实现的自动执行器。

附录参考

Thunk 函数的含义和用法 - 阮一峰

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/30907.html

相关文章

  • PHP下的异步尝试二:初识协程

    摘要:如果仅依靠程序自动交出控制的话,那么一些恶意程序将会很容易占用全部时间而不与其他任务共享。多个操作可以在重叠的时间段内进行。 PHP下的异步尝试系列 如果你还不太了解PHP下的生成器,你可以根据下面目录翻阅 PHP下的异步尝试一:初识生成器 PHP下的异步尝试二:初识协程 PHP下的异步尝试三:协程的PHP版thunkify自动执行器 PHP下的异步尝试四:PHP版的Promise ...

    MudOnTire 评论0 收藏0
  • PHP下的异步尝试四:PHP的Promise

    摘要:结果打印我结论或问题这里我们基础实现了一个可以用于生产环境的后续我们会接续完善这个的特有方法,比如等后续再介绍用实现的自动执行器等附录参考中文对象入门阮一峰 PHP下的异步尝试系列 如果你还不太了解PHP下的生成器和协程,你可以根据下面目录翻阅 PHP下的异步尝试一:初识生成器 PHP下的异步尝试二:初识协程 PHP下的异步尝试三:协程的PHP版thunkify自动执行器 PHP下的...

    lentoo 评论0 收藏0
  • PHP下的异步尝试一:初识生成器

    摘要:下的异步尝试系列下的异步尝试一初识生成器下的异步尝试二初识协程下的异步尝试三协程的版自动执行器下的异步尝试四版的下的异步尝试五版的的继续完善生成器类获取迭代器当前值获取迭代器当前值返回当前产生的键生成器从上一次处继续执行重置迭代器向生成器中 PHP下的异步尝试系列 PHP下的异步尝试一:初识生成器 PHP下的异步尝试二:初识协程 PHP下的异步尝试三:协程的PHP版thunkify自...

    tomorrowwu 评论0 收藏0
  • ES6&ES7中的异步之Generator函数与异步编程

    摘要:传统的异步方法回调函数事件监听发布订阅之前写过一篇关于的文章,里边写过关于异步的一些概念。内部函数就是的回调函数,函数首先把函数的指针指向函数的下一步方法,如果没有,就把函数传给函数属性,否则直接退出。 Generator函数与异步编程 因为js是单线程语言,所以需要异步编程的存在,要不效率太低会卡死。 传统的异步方法 回调函数 事件监听 发布/订阅 Promise 之前写过一篇关...

    venmos 评论0 收藏0
  • 理解async

    摘要:写在前面本文将要实现一个顺序读取文件的最优方法,实现方式从最古老的回调方式到目前的,也会与大家分享下本人对于库与库的理解。其实的任何异步编程的解决方案的目标都是要达到同步的语义,异步的执行。 写在前面 本文将要实现一个顺序读取文件的最优方法,实现方式从最古老的回调方式到目前的async,也会与大家分享下本人对于thunk库与co库的理解。实现的效果:顺序读取出a.txt与b.txt,将...

    Jackwoo 评论0 收藏0

发表评论

0条评论

wudengzan

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<