c#异步串口编写_编写异步库-让我们将HTML转换为PDF

c#异步串口编写

This article was peer reviewed by Thomas Punt. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!

本文由Thomas Punt同行评审。 感谢所有SitePoint的同行评审人员使SitePoint内容达到最佳状态!



I can barely remember a conference where the topic of asynchronous PHP wasn’t discussed. I am pleased that it’s so frequently spoken about these days. There’s a secret these speakers aren’t telling, though…

我几乎记得一次没有讨论异步PHP主题的会议。 我很高兴这些天这么频繁地被谈论。 这些发言人没有告诉您一个秘密,不过…

Making asynchronous servers, resolving domain names, interacting with file systems: these are the easy things. Making your own asynchronous libraries is hard. And it’s where you spend most of your time!

制作异步服务器,解析域名,与文件系统交互:这很容易。 制作自己的异步库很困难。 这是您花费大部分时间的地方!

Vector image of parallel racing arrows, indicating multi-process execution

The reason those easy things are easy is because they were the proof of concept – to make async PHP competitive with NodeJS. You can see this in how similar their early interfaces were:

这些简单的事情之所以容易,是因为它们是概念的证明–使异步PHP与NodeJS竞争 。 您可以通过早期界面的相似之处看到这一点:

var http = require("http");
var server = http.createServer();server.on("request", function(request, response) {response.writeHead(200, {"Content-Type": "text/plain"});response.end("Hello World");
});server.listen(3000, "127.0.0.1");

This code was tested with Node 7.3.0

此代码已在Node 7.3.0进行了测试

require "vendor/autoload.php";$loop = React\EventLoop\Factory::create();
$socket = new React\Socket\Server($loop);
$server = new React\Http\Server($socket);$server->on("request", function($request, $response) {$response->writeHead(200, ["Content-Type" => "text/plain"]);$response->end("Hello world");
});$socket->listen(3000, "127.0.0.1");
$loop->run();

This code was tested with PHP 7.1 and react/http:0.4.2

这段代码已经用PHP 7.1进行了测试,并进行了react/http:0.4.2

Today, we’re going to look at a few ways to make your application code work well in an asynchronous architecture. Fret not – your code can still work in a synchronous architecture, so you don’t have to give anything up to learn this new skill. Apart from a bit of time…

今天,我们将研究几种使您的应用程序代码在异步体系结构中正常工作的方法。 不用-您的代码仍然可以在同步体系结构中工作,因此您无需付出任何努力就可以学习这项新技能。 除了一点时间...

You can find the code for this tutorial on Github. I’ve tested it with PHP 7.1 and the most recent versions of ReactPHP and Amp.

您可以在Github上找到本教程的代码。 我已经用PHP 7.1以及ReactPHP和Amp的最新版本对其进行了测试。

有前途的理论 (Promising Theory)

There are a few abstractions common to asynchronous code. We’ve already seen one of them: callbacks. Callbacks, by their very name, describe how they treat slow or blocking operations. Synchronous code is fraught with waiting. Ask for something, wait for that thing to happen.

有一些异步代码共有的抽象。 我们已经看过其中之一:回调。 回调的名称,描述了它们如何对待缓慢或阻塞的操作。 同步代码充满了等待。 要求某事,等待那件事发生。

So, instead, asynchronous frameworks and libraries can employ callbacks. Ask for something, and when it happens: the framework or library will call your code back.

因此,异步框架和库可以使用回调。 询问一些事情,以及什么时候发生:框架或库将回调您的代码。

In the case of HTTP servers, we don’t preemptively handle all requests. We don’t wait around for requests to happen, either. We simply describe the code that should be called, should a request happen. The event loop takes care of the rest.

对于HTTP服务器,我们不会抢先处理所有请求。 我们也不会等待请求发生。 我们仅描述发生请求时应调用的代码。 事件循环负责其余的工作。

A second common abstraction is promises. Where callbacks are hooks waiting for future events, promises are references to future values. They look something like this:

第二种常见的抽象是承诺。 在回调是等待未来事件的钩子的地方,promise是对未来值的引用。 他们看起来像这样:

readFile()->then(function(string $content) {print "content: " . $content;})->catch(function(Exception $e) {print "error: " . $e->getMessage();});

It’s a bit more code than callbacks alone, but it’s an interesting approach. We wait for something to happen, and then do another thing. If something goes wrong, we catch the error and respond sensibly. This may look simple, but it’s not spoken about nearly enough.

它比单独的回调要多得多的代码,但这是一种有趣的方法。 我们等待某些事情发生, 然后再做另一件事。 如果出现问题,我们发现错误并做出明智的响应。 这看起来很简单,但说得还不够多。

We’re still using callbacks, but we’ve wrapped them in an abstraction which helps us in other ways. One such benefit is that they allow multiple resolution callbacks…

我们仍在使用回调,但是我们将它们包装在抽象中,以其他方式帮助我们。 这样的好处之一是它们允许多个分辨率的回调…

$promise = readFile();
$promise->then(...)->catch(...);// ...let's add logging to existing code$promise->then(function(string $content) use ($logger) {$logger->info("file was read");
});

There’s something else I’d like us to focus on. It’s that promises provide a common language – a common abstraction – for thinking about how synchronous code can become asynchronous code.

我还希望我们重点关注其他事情。 承诺为思考同步代码如何成为异步代码提供了一种通用语言-一种通用抽象。

Let’s take some application code and make it asynchronous, using promises…

让我们使用一些应用程序代码,并使用promises使其异步。

制作PDF文件 (Making PDF Files)

It’s common for applications to generate some kind of summary document – be it an invoice or stock list. Imagine you have an e-commerce application which processes payments through Stripe. When customers purchase something, you’d like them to be able to download a PDF receipt of that transaction.

应用程序通常会生成某种摘要文档,无论是发票还是库存清单。 假设您有一个电子商务应用程序,该应用程序通过Stripe处理付款。 客户购买商品时,您希望他们能够下载该交易的PDF收据。

There are many ways you could do this, but a really simple approach would be to generate the document using HTML and CSS. You could convert that to a PDF document, and allow the customer to download it.

您可以通过多种方法来执行此操作,但是真正简单的方法是使用HTML和CSS生成文档。 您可以将其转换为PDF文档,并允许客户下载。

I needed to do something similar recently. I discovered that there aren’t many good libraries that support this kind of operation. I couldn’t find a single abstraction which would allow me to switch between different HTML → PDF engines. So I started to build my own.

最近我需要做类似的事情。 我发现没有很多好的库支持这种操作。 我找不到一个单一的抽象就可以在不同HTML→PDF引擎之间进行切换。 所以我开始建立自己的。

I began thinking about what I needed the abstraction to do. I settled on an interface quite like:

我开始考虑我需要抽象做什么。 我选择了一个非常像这样的界面:

interface Driver
{public function html($html = null);public function size($size = null);public function orientation($orientation = null);public function dpi($dpi = null);public function render();
}

For the sake of simplicity, I wanted all but the render method to function as both getters and setters. Given this set of expected methods, the next thing to do was to create an implementation, using one possible engine. I added domPDF to my project, and set about using it:

为了简单起见,我希望除render方法之外的所有方法都可以同时用作getter和setter。 有了这套预期的方法,接下来要做的就是使用一个可能的引擎创建一个实现。 我将domPDF添加到我的项目中,并开始使用它:

class DomDriver extends BaseDriver implements Driver
{private $options;public function __construct(array $options = []){$this->options = $options;}public function render(){$data = $this->data();$custom = $this->options;return $this->parallel(function() use ($data, $custom) {$options = new Options();$options->set("isJavascriptEnabled", true);$options->set("isHtml5ParserEnabled", true);$options->set("dpi", $data["dpi"]);foreach ($custom as $key => $value) {$options->set($key, $value);}$engine = new Dompdf($options);$engine->setPaper($data["size"], $data["orientation"]);$engine->loadHtml($data["html"]);$engine->render();return $engine->output();});}
}

I’m not going to go into the specifics of how to use domPDF. I think the docs do a good enough job of that, allowing me to focus on the async bits of this implementation.

我将不涉及如何使用domPDF的细节。 我认为文档在这方面做得很好,让我可以专注于此实现的异步位。

We’ll look at the data and parallel methods in a bit. What’s important about this Driver implementation is that it gathers the data (if any have been set, otherwise defaults) and custom options together. It passes these to a callback we’d like to be run asynchronously.

我们将稍后介绍dataparallel方法。 此Driver实现的重要意义在于,它将数据(如果已设置,否则设置为默认值)和自定义选项一起收集。 它将这些传递给我们希望异步运行的回调。

domPDF isn’t an asynchronous library, and converting HTML → PDF is a notoriously slow process. So how do we make it asynchronous? Well, we could write a completely asynchronous converter, or we could use an existing synchronous converter; but run it in a parallel thread or process.

domPDF不是一个异步库,因此转换HTML→PDF的过程非常缓慢。 那么我们如何使其异步? 好吧,我们可以编写一个完全异步的转换器,也可以使用现有的同步转换器。 但要在并行线程或进程中运行它。

That’s what I made the parallel method for:

那就是我为以下方法制作parallel方法的原因:

abstract class BaseDriver implements Driver
{protected $html = "";protected $size = "A4";protected $orientation = "portrait";protected $dpi = 300;public function html($body = null){return $this->access("html", $html);}private function access($key, $value = null){if (is_null($value)) {return $this->$key;}$this->$key = $value;return $this;}public function size($size = null){return $this->access("size", $size);}public function orientation($orientation = null){return $this->access("orientation", $orientation);}public function dpi($dpi = null){return $this->access("dpi", $dpi);}protected function data(){return ["html" => $html,"size" => $this->size,"orientation" => $this->orientation,"dpi" => $this->dpi,];}protected function parallel(Closure $deferred){// TODO}
}

Here I implemented the getter-setter methods, figuring that I could reuse them for the next implementation. The data method acts as shortcut for collecting various document properties into an array, making them easier to pass to anonymous functions.

在这里,我实现了getter-setter方法,并确定可以将其重用于下一个实现。 data方法是将各种文档属性收集到数组中的快捷方式,使它们更易于传递给匿名函数。

The parallel method started to get interesting:

parallel方法开始变得有趣起来:

use Amp\Parallel\Forking\Fork;
use Amp\Parallel\Threading\Thread;// ...protected function parallel(Closure $deferred)
{if (Fork::supported()) {return Fork::spawn($deferred)->join();}if (Thread::supported()) {return Thread::spawn($deferred)->join();}return null;
}

I’m a huge fan of the Amp project. It’s a collection of libraries supporting asynchronous architecture, and they’re key supporters of the async-interop project.

我是Amp项目的忠实拥护者 。 它是支持异步体系结构的库的集合,它们是async-interop项目的主要支持者。

One of their libraries is called amphp/parallel, and it supports multi-threaded and multi-process code (via Pthreads and Process Control extensions). Those spawn methods return Amp’s implementation of promises. That means the render method can be used like any other promise-returning method:

他们的库之一叫做amphp/parallel ,它支持多线程和多进程代码(通过Pthreads和Process Control扩展)。 这些spawn方法返回Amp的promise实现。 这意味着render方法可以像其他任何promise-returning方法一样使用:

$promise = $driver->html("

hello world

")->size("A4")->orientation("portrait")->dpi(300)->render();$results = yield $promise;

This code is a bit loaded. Amp also provides an event loop implementation and all the helper code to be able to convert ordinary PHP generators to coroutines and promises. You can read about how this is even possible, and what it has to do with PHP’s generators in another post I’ve written.

此代码有点加载。 Amp还提供了事件循环实现和所有帮助程序代码,能够将普通PHP生成器转换为协程和Promise。 在我写的另一篇文章中 ,您可以了解到这是怎么可能的,以及它与PHP的生成器有什么关系。

The returned promises are also becoming standardized. Amp returns implementations of the Promise spec. It deviates slightly from the code I showed above, but still performs the same function.

返还的承诺也正在变得标准化。 Amp返回Promise规范的实现 。 它与我上面显示的代码略有不同,但是仍然执行相同的功能。

Generators work like coroutines from languages that have them. Coroutines are interruptible functions, which means they can be used to do short bursts of work, and then pause while they wait for something. While paused, other functions can use the system resources.

生成器的工作方式就像协程来自具有它们的语言一样。 协程是可中断的功能,这意味着它们可以用于做短暂的工作,然后在等待某事时暂停。 暂停时,其他功能可以使用系统资源。

In practice, this looks like:

实际上,这看起来像:

use AsyncInterop\Loop;Loop::execute(Amp\wrap(function() {$result = yield funcReturnsPromise();})
);

This looks way more complicated than just writing synchronous code to begin with. But what it allows for is that other things can happen while we would otherwise be waiting for funcReturnsPromise to complete.

这看起来比开始编写同步代码要复杂得多。 但是它允许在其他情况下可能发生其他事情,否则我们将等待funcReturnsPromise完成。

Yielding promises is that abstraction I was talking about. It gives us the framework by which we can make functions that return promises. Code can interact with those promises in predictable and understandable ways.

兑现承诺就是我所说的抽象。 它为我们提供了一个框架,通过该框架我们可以制作返回承诺的函数。 代码可以以可预测和可理解的方式与这些承诺进行交互。

Look at what it would be like to render PDF documents using our driver:

看看使用我们的驱动程序渲染PDF文档是什么样的:

use AsyncInterop\Loop;Loop::execute(Amp\wrap(function() {$driver = new DomDriver();// this is an AsyncInterop\Promise...$promise = $driver->body("

hello world

")->size("A4")->orientation("portrait")->dpi(300)->render();$results = yield $promise;// write $results to an empty PDF file }));

This is less useful than, say, generating PDFs in an asynchronous HTTP server. There’s an Amp library called Aerys which makes these kinds of servers easier to create. Using Aerys, you could create the following HTTP server code:

这比在异步HTTP服务器中生成PDF有用。 有一个称为Aerys的Amp库,它使这类服务器的创建更加容易。 使用Aerys,您可以创建以下HTTP服务器代码:

$router = new Aerys\Router();$router->get("/", function($request, $response) {$response->end("

Hello World!

"); });$router->get("/convert", function($request, $response) {$driver = new DomDriver();// this is an AsyncInterop\Promise...$promise = $driver->body("

hello world

")->size("A4")->orientation("portrait")->dpi(300)->render();$results = yield $promise;$response->setHeader("Content-type", "application/pdf")->end($results); });(new Aerys\Host())->expose("127.0.0.1", 3000)->use($router);

Again, I’m not going to go into the details of Aerys now. It’s an impressive bit of software, well deserving of it’s own post. You don’t need to understand how Aerys works in order to see how natural our converter’s code looks alongside it.

再说一次,我现在不讨论Aerys的细节。 这是一个令人印象深刻的软件,值得拥有。 您无需了解Aerys的工作原理即可了解转换器代码与代码一起看起来的自然程度。

我的老板说“没有异步!” (My Boss Says “No Async!”)

Why go through all this trouble, if you’re unsure how often you’ll be able to build asynchronous applications? Writing this code gives us valuable insight into a new programming paradigm. And, just because we’re writing this code as asynchronous doesn’t mean it can’t work in synchronous environments.

如果您不确定能够多久构建一次异步应用程序,为什么还要经历所有这些麻烦呢? 编写此代码为我们提供了对新编程范例的宝贵见解。 而且,仅因为我们将这段代码编写为异步代码并不意味着它无法在同步环境中工作。

To use this code in a synchronous application, all we need to do is move some of the asynchronous code inside:

要在同步应用程序中使用此代码,我们要做的就是在其中移动一些异步代码:

use AsyncInterop\Loop;class SyncDriver implements Driver
{private $decorated;public function __construct(Driver $decorated){$this->decorated = $decorated;}// ...proxy getters/setters to $decoratedpublic function render(){$result = null;Loop::execute(Amp\wrap(function() use (&$result) {$result = yield $this->decorated->render();}));return $result;}
}

Using this decorator, we can write what appears to be synchronous code:

使用此装饰器,我们可以编写看似同步的代码:

$driver = new DomDriver();// this is a string...
$results = $driver->body("

hello world

")->size("A4")->orientation("portrait")->dpi(300)->render();// write $results to an empty PDF file

It’s still running the code asynchronously (in the background at least), but none of that is exposed to the consumer. You could use this in a synchronous application, and never know what was going on under the hood.

它仍在异步运行代码(至少在后台运行),但没有一个暴露给使用者。 您可以在同步应用程序中使用它,而永远不知道底层发生了什么。

支持其他框架 (Supporting Other Frameworks)

Amp has a particular set of requirements that make it unsuitable for all environments. For example, the base Amp (event loop) library requires PHP 7.0. The parallel library requires the Pthreads extension or the Process Control extension.

Amp具有一组特定要求,使其不适用于所有环境。 例如,基本的Amp(事件循环)库需要PHP 7.0 。 并行库需要Pthreads扩展或Process Control扩展。

I didn’t want to impose these restrictions on everyone, and wondered how I could support a wider range of systems. The answer was to abstract the parallel execution code into another driver system:

我不想对所有人施加这些限制,并且想知道如何支持更广泛的系统。 答案是将并行执行代码抽象到另一个驱动程序系统中:

interface Runner
{public function run(Closure $deferred);
}

I could implement this for Amp as well as for the (less restrictive, albeit much older) ReactPHP:

我可以为Amp以及(尽管限制更小,虽然更老)ReactPHP实现此功能:

use React\ChildProcess\Process;
use SuperClosure\Serializer;class ReactRunner implements Runner
{public function run(Closure $deferred){$autoload = $this->autoload();$serializer = new Serializer();$serialized = base64_encode($serializer->serialize($deferred));$raw = "require_once '{$autoload}';\$serializer = new SuperClosure\Serializer();\$serialized = base64_decode('{$serialized}');return call_user_func(\$serializer->unserialize(\$serialized));";$encoded = addslashes(base64_encode($raw));$code = sprintf("print eval(base64_decode('%s'));",$encoded);return new Process(sprintf("exec php -r '%s'",addslashes($code)));}private function autoload(){$dir = __DIR__;$suffix = "vendor/autoload.php";$path1 = "{$dir}/../../{$suffix}";$path2 = "{$dir}/../../../../{$suffix}";if (file_exists($path1)) {return realpath($path1);}if (file_exists($path2)) {return realpath($path2);}}
}

I’m used to passing around closures to multi-threaded and multi-process workers, because that’s how Pthreads and Process Control work. Using ReactPHP Process objects is entirely different as they rely on exec for multi-process execution. I decided to implement the same closure functionality I was used to. This isn’t essential to asynchronous code – it’s purely an expression of taste.

我习惯于将闭包传递给多线程和多进程工作程序,因为这就是Pthreads和Process Control的工作方式。 使用ReactPHP Process对象完全不同,因为它们依赖exec来执行多进程。 我决定实现与以前相同的关闭功能。 这对于异步代码不是必需的–纯粹是一种品味的表达。

The SuperClosure library serializes closures and their bound variables. Most of the code here is what you’d expect to find inside a worker script. In fact, the only way (apart from serializing closures) to use ReactPHP’s child process library is to send tasks to a worker script.

SuperClosure库对闭包及其绑定变量进行序列化。 这里的大多数代码是您希望在工作脚本中找到的代码。 实际上,使用ReactPHP的子进程库的唯一方法(除了序列化闭包之外)是将任务发送到辅助脚本。

Now, instead of loading our drivers with $this->parallel and Amp-specific code, we can pass runner implementations around. As async code, this resembles:

现在,我们无需传递$this->parallel和Amp特定代码的驱动程序,而是可以传递运行程序实现。 作为异步代码,它类似于:

use React\EventLoop\Factory;$driver = new DomDriver();$runner = new ReactRunner();// this is a React\ChildProcess\Process...
$process = $driver->body("

hello world

")->size("A4")->orientation("portrait")->dpi(300)->render($runner);$loop = Factory::create();$process->on("exit", function() use ($loop) {$loop->stop(); });$loop->addTimer(0.001, function($timer) use ($process) {$process->start($timer->getLoop());$process->stdout->on("data", function($results) {// write $results to an empty PDF file}); });$loop->run();

Don’t be alarmed by how different this ReactPHP code looks from the Amp code. ReactPHP doesn’t implement the same coroutine foundation as Amp does. Instead, ReactPHP favors callbacks for most things. This code is still just running the PDF conversion in parallel, and returning the resulting PDF data.

不用担心此ReactPHP代码与Amp代码有何不同。 ReactPHP没有实现与Amp相同的协程基础。 相反,ReactPHP在大多数情况下都倾向于使用回调。 这段代码仍然只是并行运行PDF转换,并返回生成的PDF数据。

With runners abstracted, we can use any asynchronous framework we’d like, and we can expect the abstractions of that framework to be returned by the driver we’re using.

将运行程序抽象化之后,我们可以使用我们想要的任何异步框架,并且可以预期该框架的抽象将由我们使用的驱动程序返回。

我可以使用这个吗? (Can I use This?)

What started out as an experiment became a multi-driver, multi-runner HTML → PDF library; called Paper. It’s like the HTML → PDF equivalent of Flysystem, but it’s also a good example of how to write asynchronous libraries.

最初的实验变成了多驱动程序,多运行者HTML→PDF库; 叫纸 。 就像FlysystemHTML→PDF 一样 ,但这也是如何编写异步库的一个很好的例子。

As you try to make async PHP applications, you’re going to find gaps in the library ecosystem. Don’t be discouraged by these! Instead, take the opportunity to think about how you’d make your own asynchronous libraries, using the abstractions ReactPHP and Amp provide.

当您尝试制作异步PHP应用程序时,您将发现库生态系统中的空白。 这些不要气!! 取而代之的是,利用ReactPHP和Amp提供的抽象思想来思考如何创建自己的异步库。

Have you built an interesting async PHP application or library recently? Let us know in the comments.

您最近是否构建了一个有趣的异步PHP应用程序或库? 让我们在评论中知道。

翻译自: https://www.sitepoint.com/writing-async-libraries-lets-convert-html-to-pdf/

c#异步串口编写


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部