使用Guzzle HTTP客户端的多线程下载文件:EachPromises vs Pool对象

时间:2018-07-16 12:21:14

标签: php multithreading http guzzle

出于测试目的,我有2000个图像URI(字符串)的数组,我使用此功能异步下载。经过一些谷歌搜索和测试并尝试,我想出了两个都可以工作的 2个函数(坦白地说, downloadFilesAsync2 会抛出一个 InvalidArgumentException 在最后一行)。

函数 downloadFilesAsync2 基于类 GuzzleHttp \ Promise \ EachPromise ,而 downloadFilesAsync1 基于 GuzzleHttp \ Pool < / strong>课程。

这两个函数都能很好地异步下载2000个文件,同时最多可以下载10个线程。

我知道它们可以工作,但是没有别的。我想知道是否有人可以解释这两种方法,一种方法是否优于另一种方法,含义等等。

from PIL import Image
import urllib.request
import skimage

def f(row):

    URL=row['ThumbnailURL']

    #URL = 'http://www.moma.org/media/W1siZiIsIjU5NDA1Il0sWyJwIiwiY29udmVydCIsIi1yZXNpemUgMzAweDMwMFx1MDAzZSJdXQ.jpg?sha=137b8455b1ec6167'

    with urllib.request.urlopen(URL) as url:
        with open('temp.jpg', 'wb') as f:
            f.write(url.read())

    tutu = Image.open('temp.jpg')

    val=skimage.exposure.is_low_contrast(tutu, fraction_threshold=0.4, lower_percentile=1, upper_percentile=99, method='linear')

    return val

data3['lowcontornot'] = data3.apply(f, axis=1)

1 个答案:

答案 0 :(得分:1)

首先,我将解决downloadFilesAsync2方法中的 InvalidArgumentException 。这种方法实际上存在两个问题。两者都与此有关:

$requests[] = $client->request('GET', $uri, ['sink' => $loc]);

第一个问题是Client::request()是包装$client->requestAsync()->wait()的同步实用程序方法。 $client->request()将返回Psr\Http\Message\ResponseInterface的一个实例,结果$requests[]实际上将被ResponseInterface的实现所填充。这就是最终导致 InvalidArgumentException 的原因,因为$requests不包含任何Psr\Http\Message\RequestInterface,并且从Pool::__construct()内部引发了异常。

此方法的更正版本应包含类似于以下内容的代码:

$requests = [
    new Request('GET', 'www.google.com', [], null, 1.1),
    new Request('GET', 'www.ebay.com', [], null, 1.1),
    new Request('GET', 'www.cnn.com', [], null, 1.1),
    new Request('GET', 'www.red.com', [], null, 1.1),
];

$pool = new Pool($client, $requests, [
    'concurrency' => 10,
    'fulfilled' => function(ResponseInterface $response) {
        // do something
    },
    'rejected' => function($reason, $index) {
        // do something error handling
    },
    'options' => ['sink' => $some_location,],
]);

$promise = $pool->promise();
$promise->wait();

要回答第二个问题“这两种方法之间有什么区别”,答案很简单,没有答案。为了解释这一点,让我复制并粘贴Pool::__construct()

/**
 * @param ClientInterface $client   Client used to send the requests.
 * @param array|\Iterator $requests Requests or functions that return
 *                                  requests to send concurrently.
 * @param array           $config   Associative array of options
 *     - concurrency: (int) Maximum number of requests to send concurrently
 *     - options: Array of request options to apply to each request.
 *     - fulfilled: (callable) Function to invoke when a request completes.
 *     - rejected: (callable) Function to invoke when a request is rejected.
 */
public function __construct(
    ClientInterface $client,
    $requests,
    array $config = []
) {
    // Backwards compatibility.
    if (isset($config['pool_size'])) {
        $config['concurrency'] = $config['pool_size'];
    } elseif (!isset($config['concurrency'])) {
        $config['concurrency'] = 25;
    }

    if (isset($config['options'])) {
        $opts = $config['options'];
        unset($config['options']);
    } else {
        $opts = [];
    }

    $iterable = \GuzzleHttp\Promise\iter_for($requests);
    $requests = function () use ($iterable, $client, $opts) {
        foreach ($iterable as $key => $rfn) {
            if ($rfn instanceof RequestInterface) {
                yield $key => $client->sendAsync($rfn, $opts);
            } elseif (is_callable($rfn)) {
                yield $key => $rfn($opts);
            } else {
                throw new \InvalidArgumentException('Each value yielded by '
                    . 'the iterator must be a Psr7\Http\Message\RequestInterface '
                    . 'or a callable that returns a promise that fulfills '
                    . 'with a Psr7\Message\Http\ResponseInterface object.');
            }
        }
    };

    $this->each = new EachPromise($requests(), $config);
}

现在,我们是否将其与downloadFilesAsync1方法中的简化代码进行比较:

$promises = (function () use ($client, $uris) {
    foreach ($uris as $uri) {
        yield $client->requestAsync('GET', $uri, ['sink' => $some_location]);
    }
})();
(new \GuzzleHttp\Promise\EachPromise(
    $promises, [
    'concurrency' => 10,
    'fulfilled'   => function (\Psr\Http\Message\ResponseInterface $response) {
        // do something
    },
    'rejected'    => function ($reason, $index) {
        // do something
    },
])
)->promise()->wait();

在两个示例中,都有一个生成承诺的生成器,该承诺可以解析为ResponseInterface的实例,并且该生成器连同配置数组(已完成可调用,已拒绝可调用,并发)也被馈送到{ {1}}。

总结:

  1. EachPromise在功能上与使用downloadFilesAsync1相同,只是没有Pool内置的错误检查。

  2. Pool::__construct()中存在一些错误,这些错误会导致在实例化downloadFilesAsync2时,在接收到 InvalidArgumentException 之前,以同步方式下载文件。

我唯一的建议是:使用对您而言更直观的方式。