首页 > 解决方案 > Amp PHP - 协作多任务/异步睡眠

问题描述

在单线程 PHP 中,我需要编写一个应用程序:

a) 和 b) 必须工作,即使服务器正在等待/服务请求或 HTTP 客户端正在等待回复

我想出了一个使用 PHP Amp 服务器的想法。它工作得很好。

但是对于 HTTP 客户端,我需要使用 PHP curl。

我的代码如下所示:

...
$mh = curl_multi_init();
curl_multi_add_handle($mh, $ch);
do {
    AmpLoopHelper::asyncSleep(0.001);
    $mrc = curl_multi_exec($mh, $isRunning);
} while ($isRunning && ($mrc == CURLM_CALL_MULTI_PERFORM || $mrc == CURLM_OK));
...

和自定义AmpLoopHelper类:

<?php

namespace Mvorisek\Dsv;

use Amp\Loop;
use Amp\Loop\Driver;


class AmpLoopHelper {
    /** @var int|null */
    private static $dummyWatcherId;

    /**
     * Async sleep and keep processing of Loop tasks.<br>
     * Loop\Driver::tick() is always called at least once even if
     * the sleep delay is zero or negative.
     * 
     * @param float $sleepSecs
     */
    public static function asyncSleep(float $sleepSecs): void {
        $t = microtime(true);

        // add dummy function to repeat to prevent Loop\Driver::tick() to block
        $maxCheckDelayMillis = min(max(1, $sleepSecs * 1000 / 50), 50);
        $isNested = static::$dummyWatcherId !== null;
        if (!$isNested) {
            static::$dummyWatcherId = Loop::repeat($maxCheckDelayMillis, static function() {});
        }

        try {
            do {
                if (static::loopDriverIsRunning()) {
                    static::loopDriverTick();
                } else {
                    usleep(($sleepSecs - (microtime(true) - $t)) * 1e6);
                    break;
                }
                usleep(40);
            } while(microtime(true) - $t < $sleepSecs);
        } finally {
            if (!$isNested) {
                Loop::cancel(static::$dummyWatcherId);
            }
        }
    }

    private static function loopDriverGet(): Driver {
        return \Closure::bind(static function() {
            return Loop::$driver;
        }, null, Loop::class)();
    }

    private static function loopDriverIsRunning(): bool {
        $driver = static::loopDriverGet();
        return \Closure::bind(static function() use($driver) {
            return $driver->running;
        }, null, Driver::class)();
    }

    private static function loopDriverTick(): void {
        $driver = static::loopDriverGet();
        \Closure::bind(static function() use($driver) {
            $driver->tick();
        }, null, Driver::class)();
    }
}

但有时无法访问 HTTP 服务器。助手类使用了 Amp 类的一些私有方法。

想法asyncSleep正确吗?

标签: phpasync-await

解决方案


问题是Driver->dispatch()可以用阻塞标志调用,并且阻塞流没有从我的助手类中更新。


推荐阅读