日月神话
或者:异步/等待不是您所想的。 (Or: async/await is not what you think it is.)
Make no mistake: we are living through a new asynchronous programming renaissance. The programming community has spent the 2010s rediscovering the techniques that we used to use to handle highly-concurrent workloads when we didn’t have the memory available to spawn an operating system thread to handle every single connection. This trend is incredibly apparent when we look at the languages and language features that are heavily discussed and debated. Consider the meteoric rise of Node.JS (the ultimate async-first language), or the incredible prominence of the goroutine and green threading primitives in Go, or the incredible number of languages that are hopping on the async
/await
train that C♯ got started.
没错:我们正在经历新的异步编程复兴。 当我们没有足够的内存来产生操作系统线程来处理每个连接时,编程社区在2010年代就重新发现了用于处理高并发工作负载的技术。 当我们查看经过大量讨论和辩论的语言和语言功能时,这种趋势非常明显。 考虑一下Node.JS(最终的异步优先语言)的迅猛发展,或者Go语言中goroutine和绿色线程原语的惊人表现,或者C♯获得的async
/ await
训练中跳跃的语言数量之多。开始了 。
While there are many interesting things to talk about in that prior list of languages and features, I really want to focus on the last one: async
/await
. In particular, I want to talk about a fairly popular blog post that takes aim at the async
/await
syntactic feature: What Color is Your Function?. For those who are new to the topic, I’ll very briefly explain what async
/await
are. However, I will not summarise the prior blog post: it’s a good piece of work and should be read in its entirety. If you’re totally unfamiliar with this discussion, read my little async
/await
summary, then read the blog post, then come back here. I promise the blog post won’t go away while you’re gone.
在先前的语言和功能列表中有很多有趣的事情要谈论 ,我真的很想关注最后一个: async
/ await
。 特别是,我想谈谈一个颇受欢迎的博客文章,该文章针对async
/ await
语法功能: 您的功能是什么颜色? 。 对于那些刚接触该主题的人,我将非常简要地解释什么是async
/ await
。 但是,我不会总结以前的博客文章:这是一项很好的工作,应完整阅读。 如果您完全不熟悉此讨论,请阅读我的async
/ await
摘要,然后阅读博客文章,然后返回此处。 我保证您离开后该博客文章不会消失。
One last note: I am explicitly discussing only concurrent programming here, not parallel programming. If you’re not comfortable with this distinction, go watch Rob Pike explain the difference and come back. Fundamentally, this discussion is related to parallelism but not really affected by it in any profound way.
最后一点:我在这里只讨论并发编程,不讨论并行编程。 如果您对这种区别不满意,请观看Rob Pike解释区别并返回。 从根本上讲,此讨论与并行性有关,但并未真正受到任何深远的影响。
异步/等待入门 (An Async/Await Primer)
What is async
/await
at a high level?
什么是async
/高层await
?
Basically, they’re tools for explicitly marking concurrent control flow using language keywords. If you have an operation that is going to need to wait for some I/O or for some background process to complete, you can wrap that in a function (call it “function A”) and mark that function as async
. Then, you can call that function from some other function (“function B”) using the keyword await
, which is a signal to the language runtime that it should pause execution of function B until function A’s I/O has completed.
基本上,它们是使用语言关键字显式标记并发控制流的工具。 如果您有需要等待一些I / O或某些后台进程完成的操作,则可以将其包装在一个函数中(称为“函数A”)并将该函数标记为async
。 然后,您可以使用关键字await
从其他功能(“功能B”)中调用该功能,这是向语言运行时发出的信号,它应暂停功能B的执行,直到功能A的I / O完成为止。
Put another way, async
/await
are keywords that signal to the language that your code is willing to pause its execution at this point to let other code run. It’s cooperative multitasking!
换句话说, async
/ await
是关键字,用于向语言表示您的代码愿意在此时暂停执行以让其他代码运行。 这是协作式多任务处理 !
Under the hood of different languages async
/await
do different things, but the net effect is always that they allow you to write concurrent code in a somewhat linear manner. In effectively-single-threaded languages like Python, they let you turn a series of callbacks written as individual functions into a series of what appear to be mostly ordinary functions but magically can yield their flow of control to someone else. This is a neat trick, and it’s extremely helpful from the perspective of clearing up some of the downsides of traditional concurrent code: specifically, callback hell.
在不同语言的背景下, async
/ await
做不同的事情,但是最终的结果是,它们总是允许您以某种线性方式编写并发代码。 在像Python这样的有效单线程语言中,它们使您可以将作为单个函数编写的一系列回调转换为一系列似乎大部分为普通函数的回调,但可以神奇地将它们的控制流传递给其他人。 这是一个巧妙的窍门,从清除传统并发代码的一些缺点(特别是回调地狱)的角度来看,这非常有用。
That’s all the time I’m willing to spend on this primer here. If you want more detail on how they work in Python (and I recommend you get it, this is a cool idea well worth understanding), Brett Cannon has a great blog post that covers this.
我一直都愿意在这里花这本入门书。 如果您想更详细地了解它们在Python中的工作方式(我建议您理解它,这是一个很值得理解的好主意), Brett Cannon的博客文章很不错 。
已经到点 (Get To The Point Already)
So, lets talk about function colour. Bob Nystrom’s great blog post about function colour that I linked above uses the idea of coloured functions to talk about the fact that the async
/await
syntactic feature effectively means that a programming language has two types (or “colours”) of function. One type is what you’d think of as your standard function: for the rest of this post I’ll call that a “synchronous” function. The other type is a function that is annotated as async
: what we’ll call (inventively) “asynchronous” functions.
因此,让我们谈谈功能颜色。 我上面链接的鲍勃·尼斯特罗姆(Bob Nystrom)在有关函数颜色的精彩博客文章中使用了函数颜色的概念来谈论async
/ await
句法功能实际上意味着编程语言具有两种类型(或“颜色”)函数的事实。 一种类型是您认为是标准函数的类型:在本文的其余部分中,我将其称为“同步”函数。 另一种类型是被标注为async
的函数:我们将(创造性地)称为“异步”函数。
Bob points out that there are rules for these kinds of function. Specifically, while synchronous functions can be called from any kind of function, asynchronous functions must be called only from other asynchronous functions by means of the await
keyword. Put in Python-specific terms, consider the following functions:
鲍勃指出这类功能有规定。 具体来说,尽管可以从任何一种函数中调用同步函数,但必须仅通过await
关键字从其他异步函数中调用异步函数。 用特定于Python的术语,请考虑以下功能:
def function_a(some_socket_or_something): return some_socket_or_something.recv(8192) async def function_b(some_streamreader_or_something): return (await some_streamreader_or_something.read(8192))
def function_a(some_socket_or_something): return some_socket_or_something.recv(8192) async def function_b(some_streamreader_or_something): return (await some_streamreader_or_something.read(8192))
If you want to call function_a
, you can call that from anywhere, even inside function_b
. However, you cannot call function_b
from within function_a
. In Python, if you try to do this, you’ll find that function_b
never actually executes: it returns immediately and does nothing. In more statically-typed languages like C♯, you’ll get a compiler error instead. Python’s behaviour here (subtly failing to execute your code) is actually one of the most common bugs hit by people using Python async functions: forgetting to annotate them with await
.
如果要调用function_a
,则可以在任何地方(甚至在function_b
内部) function_b
。 但是,你不能叫function_b
从内部function_a
。 在Python中,如果尝试执行此操作,则会发现function_b
从未真正执行过:它会立即返回并且不执行任何操作。 在像C♯这样的更多静态类型的语言中,您会收到编译器错误。 实际上,Python的行为(可能无法执行代码)实际上是人们使用Python异步函数遇到的最常见的错误之一:忘记使用await
对其进行注释。
From Bob’s perspective, this dichotomy makes your life sufficiently hard that it is more-or-less unbearable (specifically, Bob will “start grinding [his] teeth” when he hears about this kind of thing). That’s fair enough: people like what they like, and there is nothing wrong with saying that you’d rather approach this a different way. People should write code in whatever way makes them most productive, and if that means you’d like to use thousands of threads and write in Visual Basic 6 then hell, you go Glen Coco. Godspeed.
从鲍勃的角度来看,这种二分法使您的生活变得十分艰难,以至于变得或多或少难以忍受(特别是,鲍勃在听到这种事情时会“开始磨牙”)。 这很公平:人们喜欢他们喜欢的东西,并且说您宁愿以不同的方式来对待也没有错。 人们应该以任何使他们效率最高的方式编写代码,如果这意味着您想使用数千个线程并用Visual Basic 6编写,那么该死的是Glen Coco。 上帝的速度。
However, recently we have seen discussions in the Python community around the pervasiveness of async
/await
that have rather misunderstood why async
/await
is worth having. In particular, from time to time you’ll catch someone saying that all non-threaded concurrent code (that is, all code that uses some variant of the Reactor pattern) is necessarily coloured code, and that async
/await
exist to simply make those colours obvious.
但是,最近我们在Python社区中看到了关于async
/ await
的普遍性的讨论,这些讨论已经误解了为什么async
/ await
值得拥有的原因。 特别是,您有时会发现有人说所有非线程并发代码(即,使用Reactor模式的某些变体的所有代码)都必须是彩色代码,并且存在async
/ await
只是为了使那些颜色明显。
I think that fundamentally this idea, which is parallel to Bob’s, is a severe misunderstanding of the way async code works. In the rest of this post I’d like to outline what I mean.
我认为从根本上讲,与鲍勃(Bob)相似的想法是对异步代码工作方式的严重误解。 在本文的其余部分中,我想概述一下我的意思。
颜色不是强制性的 (Colour Isn’t Mandatory)
So let’s get something out of the way right now: function colour is a tool for writing async code, not a requirement of it.
因此,让我们立即摆脱困境:函数颜色是用于编写异步代码的工具,而不是必需的工具。
This is extremely obviously true. For example, in a prior job I worked on a gigantic C codebase that was written entirely in a cooperative multitasking style. That is, it was very similar to Node.JS, but written entirely in C. Now, C does not have any method of describing function colour, nor does it have any mechanism of giving a function a colour. In C, your code executes synchronously, step by step, forever. However, we were perfectly capable of writing a real, serious application, entirely in C, that didn’t use a callstack-based resumption model like the one Bob discusses. So it’s clear that function colour isn’t intrinsic to this problem.
这显然是事实。 例如,在之前的工作中,我研究了完全以协作式多任务处理风格编写的巨大C代码库。 也就是说,它与Node.JS非常相似,但是完全用C编写。现在,C没有任何描述函数颜色的方法,也没有给函数赋颜色的任何机制。 在C语言中,您的代码永远永远同步执行。 但是,我们完全有能力完全用C编写一个真正的,认真的应用程序,而没有使用Bob所讨论的基于调用堆栈的恢复模型。 因此很明显,功能颜色不是此问题固有的。
Moreover, it’s not even intrinsic to Python. I know that Twisted is (cruelly and undeservedly) un-loved in the wider Python community, but consider a very basic Twisted program.
而且,它甚至不是Python固有的。 我知道Twisted在更广泛的Python社区中是(残酷和不当之选)不受欢迎的,但是考虑一个非常基本的Twisted程序。
This is a basic Twisted Protocol, which is responsible for managing a network connection. This is a core demonstration of how to write concurrent code: all of these functions are intended to be callbacks.
这是基本的Twisted协议 ,它负责管理网络连接。 这是如何编写并发代码的核心演示:所有这些功能均旨在作为回调。
My question is: where is the function colour here? None of these functions are async
functions, so that’s not it. Are they implicitly coloured? Well, if they were that could only be because they’d be unable to be called from synchronous code. But that’s not true at all. For example, you can use this Protocol
entirely from synchronous code:
我的问题是:这里的功能颜色在哪里? 这些功能都不是async
功能,不是吗。 它们是暗色的吗? 好吧,如果是那样的话,那只能是因为无法从同步代码中调用它们 。 但这根本不是真的。 例如,您可以完全通过同步代码使用此Protocol
:
import socket def main(): p = PrinterProtocol() s = socket.create_connection(('http2bin.org', 80)) s = s.makefile() p.connectionMade(s) while True: data = s.read(8192) if data: p.dataReceived(data) else: p.connectionLost(None) break s.close()
import socket def main(): p = PrinterProtocol() s = socket.create_connection(('http2bin.org', 80)) s = s.makefile() p.connectionMade(s) while True: data = s.read(8192) if data: p.dataReceived(data) else: p.connectionLost(None) break s.close()
There’s no async here at all! Look at all these blocking socket calls. We spend so much time waiting for the kernel to do stuff it’s crazy. All of these functions are synchronous functions in the grand tradition of synchronous functions. There is no requirement for function colour at all here.
这里根本没有异步! 查看所有这些阻塞的套接字调用。 我们花了很多时间等待内核做疯狂的事情。 在同步功能的传统中,所有这些功能都是同步功能。 此处完全不需要功能颜色。
What about Futures (or, as Twisted calls them, Deferreds)? Bob says this in his post:
期货(或如Twisted所说的Deferreds )又如何呢? 鲍勃在他的帖子中这样说:
You still can’t call a function that returns a future from synchronous code. (Well, you can, but if you do, the person who later maintains your code will invent a time machine, travel back in time to the moment that you did this and stab you in the face with a #2 pencil.)
您仍然无法调用从同步代码返回未来的函数。 (好吧,可以,但是如果这样做,后来维护代码的人会发明一台时光机,回到过去的那一刻,并用#2铅笔刺在脸上。)
Well, that statement is true-ish. It’s certainly true of asyncio.Future
, because asyncio.Future
is a Python awaitable, and also requires a running event loop to execute any code. However, it’s not true of Twisted’s Deferred
. Twisted’s Deferred
, unlike asyncio’s Future
, is not an event loop construct: it does not require an event loop to execute. When you call callback
on a Deferred
, Twisted will immediately execute any attached callbacks synchronously, in-line with the function that just fired the Deferred
. This means that Deferred
s are also not coloured objects, and you can do things like refactor the code above to this:
好吧,那句话是真实的。 asyncio.Future
当然是正确的,因为asyncio.Future
是一个可asyncio.Future
的Python,并且还需要一个运行中的事件循环来执行任何代码。 但是,Twisted的Deferred
并非如此。 与asyncio的Future
不同,Twisted的Deferred
不是事件循环构造:它不需要事件循环来执行 。 当您在Deferred
上调用callback
时,Twisted将立即同步执行任何附加的回调,并与刚刚触发Deferred
的函数保持一致。 这意味着Deferred
也不是有色对象,您可以执行以下操作来重构上面的代码:
This code also runs perfectly happily using the skeleton code I outlined above. Still no function colours here: everything is just a synchronous function call.
使用我上面概述的框架代码,该代码也可以完美地运行。 这里仍然没有函数颜色:一切都只是一个同步函数调用。
Hopefully, this will convince you that there is nothing mandatory about function colours when it comes to writing asynchronous code. You don’t have to use them. You can always construct your world out of a collection of synchronous functions that expect to run as callbacks. The only moment that function colour shows up is the moment you start making blocking I/O calls, and that there is where this discussion gets interesting.
希望这可以使您确信,在编写异步代码时,函数颜色没有强制要求。 您不必使用它们。 您始终可以根据希望作为回调运行的同步函数集合来构建自己的世界。 功能颜色显示的唯一时刻是您开始进行阻塞I / O调用的时刻,并且在此讨论中变得很有趣。
为什么我们使用功能颜色 (Why We Use Function Colour)
So hopefully I’ve just made it clear to you that you can write asynchronous code using entirely synchronous function calls: there is no requirement for coloured functions. So why does function colour come up at all?
因此,希望我已经向您阐明,您可以使用完全同步的函数调用来编写异步代码:不需要彩色函数。 那么,为什么功能色会出现呢?
A hint at the answer can be found in Glyph’s 2014 blog post Unyielding. This blog post is nominally about green threading vs regular threading, but along the way it touches on the four modes of concurrent programming:
有关答案的提示可以在Glyph的2014年博客文章Unyielding中找到 。 这篇博客文章名义上是关于绿色线程与常规线程的,但在此过程中,它涉及了并发编程的四种模式:
- Straight callbacks: Twisted’s
IProtocol
, JavaScript’son<foo>
idiom, where you give a callback to something which will call it later and then return control to something (usually a main loop) which will execute those callbacks,- “Managed” callbacks, or Futures: Twisted’s
Deferred
, JavaScript’sPromises/A[+]
, E’s Promises, where you create a dedicated result-that-will-be-available-in-the-future object and return it for the caller to add callbacks to,- Explicit coroutines: Twisted’s
@inlineCallbacks
, Tulip’syield from
coroutines, C#’sasync/await
, where you have a syntactic feature that explicitly suspends the current routine,- and finally, implicit coroutines: Java’s “green threads”, Twisted’s Corotwine, eventlet, gevent, where any function may switch the entire stack of the current thread of control by calling a function which suspends it.
- 直接回调:Twisted的
IProtocol
,JavaScript的on<foo>
习惯用法,您在其中提供回调,以便稍后进行调用,然后将控制权返回到将执行这些回调的某个事物(通常是主循环),- “托管”回调或Futures:Twisted的
Deferred
,JavaScript的Promises/A[+]
, E的Promises ,您可以在其中创建一个专用的结果,该结果将在将来可用,并将其返回给调用方添加回调,- 明确的协程:Twisted的
@inlineCallbacks
,Tulip的协程yield from
,C#的async/await
,其中您具有显式挂起当前例程的语法功能,- 最后是隐式的协程: Java的“绿色线程” ,Twisted的Corotwine , eventlet , gevent ,其中任何函数都可以通过调用挂起它的函数来切换当前控制线程的整个堆栈。
If we reframe option 4 to just say “threads”, green or otherwise, then we can write our own list:
如果我们将选项4重新组织为只说“线程”(绿色或其他),那么我们可以编写自己的列表:
- Straight callbacks: uncoloured.
- Managed callbacks: usually uncoloured, but they may be coloured in some cases.
- Explicit coroutines: usually coloured, but they may be uncoloured in some cases (e.g. Twisted’s
@inlineCallbacks
, which returns an uncolouredDeferred
). - Threads: uncoloured.
- 直接回调:无色。
- 托管回调:通常不带颜色,但在某些情况下可以带颜色。
- 显式协程:通常是彩色的,但是在某些情况下它们可能是未着色的(例如Twisted的
@inlineCallbacks
,它返回未@inlineCallbacks
的Deferred
)。 - 线程:无色。
Given that colour is clearly not mandatory for any of these, why does Bob talk so vehemently about colour?
考虑到颜色显然不是其中任何一种强制性的,为什么鲍勃如此激烈地谈论颜色?
Well, fundamentally, Bob’s argument can be reframed. He gives himself away at the end of the post, when he asks “What language isn’t colored?” and then says this:
好吧,从根本上说,鲍勃的论点可以改写。 当他问“什么颜色没有上色?”时,他在帖子的末尾放弃了自己。 然后说:
Any guess what [uncoloured langauges] have in common?
你猜[无色语言]有什么共同点吗?
Threads. Or, more precisely: multiple independent callstacks that can be switched between. It isn’t strictly necessary for them to be operating system threads. Goroutines in Go, coroutines in Lua, and fibers in Ruby are perfectly adequate.
线程。 或者,更准确地说:可以在多个独立的调用堆栈之间进行切换。 严格来说,它们不必是操作系统线程。 Go中的Goroutines,Lua中的协程和Ruby中的纤维就足够了。
So, here we get to the rub. Bob’s rant about function colour is actually a rant about proactor vs reactor programming. His argument is that it is better to write code that blocks on I/O than code that expects its I/O to be done for it elsewhere in the code. It is better to write functions that do I/O and then have their call stack switched out than it is to write functions that expect to return all the way up their call stack for the I/O to occur.
所以,在这里,我们来摸索。 鲍勃对功能颜色的抱怨实际上是对前摄者与React堆编程的抱怨。 他的观点是,编写在I / O上阻塞的代码比期望在代码的其他地方对其进行I / O的代码更好。 最好编写执行I / O的功能,然后关闭其调用堆栈,而不是编写期望一直返回其调用堆栈以使I / O发生的函数。
There are two problems with that argument.
该论点有两个问题。
One of them is that the argument is poorly framed. You can see that Bob allows “multiple independent callstacks that can be switched between”, which is exactly what async
/await
allows! The only difference is that function colours are involved. So what Bob actually wants is for multiple independent callstacks and no coloured functions. That’s fine.
其中之一是论点的框架不完善。 您会看到Bob允许“可以在多个独立的调用堆栈之间进行切换”,这正是async
/ await
允许的! 唯一的区别是涉及功能颜色。 因此Bob真正想要的是多个独立的调用堆栈,并且没有彩色函数。 没关系。
The second problem is that options 1 through 3 in my above list exist in part because of the drawbacks of threaded programming. If we allow green threading with small stack sizes as an option (à la goroutines), then the reason we avoid threading in the first place is because we are concerned about shared-state pre-emptive multitasking. As Glyph says in Unyielding:
第二个问题是上面列出的选项1至3部分存在是由于线程编程的缺点。 如果我们允许使用栈大小小的绿色线程作为选择(请参阅goroutines),那么我们之所以避免使用线程,是因为我们担心共享状态的抢先式多任务处理。 正如Glyph在《不屈》中所说:
When you’re looking at a routine that manipulates some state, in a single-tasking, nonconcurrent system, you only have to imagine the state at the beginning of the routine, and the state at the end of the routine. To imagine the different states, you need only to read the routine and imagine executing its instructions in order from top to bottom. This means that the number of instructions you must consider is n, where n is the number of instructions in the routine. By contrast, in a system with arbitrary concurrent execution – one where multiple threads might concurrently execute this routine with the same state – you have to read the method in every possible order, making the complexity nn.
在单任务非并发系统中,当您查看操纵某些状态的例程时,您只需要想象该例程开始时的状态,以及例程结束时的状态。 要想像不同的状态,您只需要阅读例程并想象按照从上到下的顺序执行其指令即可。 这意味着您必须考虑的指令数为n,其中n是例程中的指令数。 相反,在具有任意并发执行的系统中(一个多个线程可能以相同状态并发执行该例程的系统),您必须以每种可能的顺序读取该方法,从而使复杂度为n n 。
Put another way, threads are hard. Before you get your pitchforks out, note that I said hard, not impossible. If you find working with shared-state pre-emptive multitasking easy, or that it maps well onto your thought processes, more power to you. I am not here to tell you to stop using threads. I am here to tell you why we have function colours.
换句话说,线程很难。 在把干草叉拿出来之前,请注意我说的很努力,并非没有可能。 如果您发现使用共享状态的抢先式多任务处理很容易,或者它可以很好地映射到您的思维过程中,那么您将获得更多的功能。 我并不是要告诉您停止使用线程。 我在这里告诉您为什么我们要使用功能颜色。
So, if threads are hard (for some programmers), what are our other options? Well, the other option is cooperative multitasking. That is, only one bit of my code runs at once, and then it stops running to let other bits run. Options 1 through 3 of my above list give us that power. And, as I’ve demonstrated at length through this post, function colour is not necessary to achieve this kind of cooperative multitasking.
因此,如果线程很困难(对于某些程序员而言),那么我们还有其他选择吗? 好吧,另一个选择是协作多任务。 也就是说,我的代码只有一次运行一次,然后停止运行以让其他代码运行。 我上面列出的选项1到3给了我们这种力量。 而且,正如我在这篇文章中详细说明的那样,实现这种协作式多任务处理并不需要颜色功能。
What it is, though, is convenient! That’s really it. Despite Bob’s many arguments about how annoying async
/await
is, at least in Python it reinstates to the world of cooperative multitasking the one thing he says he wants: multiple independent callstacks that can be switched between. It does so at the cost of providing coloured functions. These are inconvenient, but in return for paying the cost of that inconvenience they allow programmers to avoid being stuck in callback hell or requiring them to reason through the complex ways a callback chain can fire or propagate errors. This makes it easier for programmers to reason about control flow and error propagation and exception flow and all the other nice things that we want to think about.
不过,这很方便! 就是这样 尽管鲍勃(Bob)对async
/ await
是多么烦人有很多争论,但至少在Python中,它恢复了协作多任务的世界,这是他说的一件事:可以在多个独立的调用堆栈之间进行切换。 这样做是以提供彩色功能为代价的。 这些操作很不方便,但作为回报,您为此付出了不便的代价,这使程序员可以避免陷入回调地狱或要求他们通过回调链可以触发或传播错误的复杂方式进行推理。 这使程序员更容易推理控制流,错误传播,异常流以及我们要考虑的所有其他美好事物。
This is why we have coloured functions: because they make lives easier for many programmers. They are an optional language tool you can use to write codebases that are easier for some programmers to reason about. For me, I am happy to work with uncoloured callbacks: I have done it my entire programming career, and I’m finally at the point where I’m really pretty good at it. But, at least anecdotally, I can tell you that many (maybe even most) programmers are not comfortable doing it: they would like the clarity that async
/await
brings.
这就是我们使用彩色函数的原因:因为它们使许多程序员的工作变得更轻松。 它们是一种可选的语言工具,可用于编写一些程序员更容易理解的代码库。 对于我来说,我很高兴使用无颜色的回调:我在整个编程生涯中都完成了这项工作,最后我真的很擅长这一点。 但是,至少可以说,我可以告诉您,许多(也许甚至是大多数)程序员都不愿意这样做:他们希望async
/ await
带来的清晰度。
However, given that we’re going to provide this convenience for our users, we should discuss briefly how to avoid the pitfalls of coloured functions. Because Bob is right: if you start colouring all your functions willy-nilly, you do start making things trickier.
但是,考虑到我们将为用户提供这种便利,我们应该简短地讨论如何避免使用彩色函数的陷阱。 因为鲍勃是对的:如果您开始随意着色所有功能,则确实会使事情变得棘手。
如何与彩色功能一起生活 (How To Live With Coloured Functions)
Don’t colour them.
不要给它们上色。
I’m serious: every time you write async
in your code you have made a small admission of defeat. You couldn’t come up with a clear way to construct your code that didn’t involve you needing to do some form of I/O in the middle of it. That’s fine: as programmers our job is to balance the tradeoffs between expediency (using async
whenever necessary) and good code structure (writing as little code as possible that requires I/O), and I’m sure as hell not going to judge you for choosing to put async
everywhere.
我是认真的:每次您在代码中编写async
代码时,都会承认失败。 您无法想出一种清晰的方法来构造您的代码,而无需在其中进行某种形式的I / O。 很好:作为程序员,我们的工作是在权宜之计(必要时使用async
)和良好的代码结构(编写尽可能少的需要I / O的代码)之间权衡取舍,而且我敢肯定,这不会判断您选择将async
传递到任何地方。
But you don’t need to do it. With care, there is no reason for more than about 10% of your codebase to be async
functions. Almost all the rest of your code can just transform in-memory data representations from one form to another. You don’t have to do this, and certainly if you don’t care about code reusability or the various costs of writing async
functions you should just go ahead and do that. But with care, and precision, most of your code doesn’t need it.
但是您不需要这样做。 小心一点,没有理由让代码库的大约10%以上成为async
函数。 几乎所有其他代码都可以将内存中的数据表示形式从一种形式转换为另一种形式。 您不必这样做,当然,如果您不关心代码的可重用性或编写async
函数的各种成本,则应该继续这样做。 但是经过精心和精确的处理,您的大多数代码都不需要它。
So when we have discussions about extending the async
paradigm to another Python language feature (currently, generators), we shouldn’t get bogged down in whether we’re creating two parallel worlds of functions. Whether we do that is a choice of the community. If the community is careful, we can write tools and implementations that enable async
-using programs without requiring them. We can write as much code as possible in a synchronous style. We can potentially replace the core of most of our libraries with code written like this, allowing only the tiniest amount of wrapper code to transform from synchronous to asynchronous code.
因此,当我们讨论将async
范式扩展到另一个Python语言功能(当前是generators )时,我们不应该为是否要创建两个并行的函数世界而感到困惑。 我们是否这样做是社区的选择。 如果社区谨慎,我们可以编写无需程序即可启用async
使用程序的工具和实现。 我们可以以同步方式编写尽可能多的代码 。 我们可以用这样编写的代码潜在地替换大多数库的核心,从而仅允许最少量的包装器代码从同步代码转换为异步代码。
All of this is possible. The community just needs to get comfortable with the idea that async
is a tool for improving code clarity, rather than a requirement for writing asynchronous code. And I know I’ve spoken at length about Twisted here, but I think it’s important to remember that Twisted has learnt these lessons and learnt them well. Perhaps more importantly, though, it taught those lessons to asyncio. Every time you think you’ll use asyncio’s Streams API, stop and consider whether you can use Protocols instead. Because streams are coloured, but Protocols aren’t, and using Protocols will force you into a style where you avoid colouring your functions unnecessarily. This gives you the opportunity to write a codebase that is protected from the difficulties of coloured functions, where your code is testable without an event loop, where it can be run in a synchronous mode if you require it, and where it provides the opportunity for substantial code reuse.
所有这些都是可能的。 社区只需要对async
是提高代码清晰度的工具而不是编写异步代码的要求的想法感到满意。 而且我知道我在这里详细谈到了Twisted,但我认为重要的是要记住Twisted已经学习了这些课程并且学得很好。 不过,也许更重要的是,它向异步学习了这些课程。 每当您认为自己将使用asyncio的Streams API时 ,请停止并考虑是否可以使用Protocols代替 。 因为流是彩色的,但协议不是彩色的,使用协议会迫使您进入一种避免不必要地给函数上色的样式。 这为您提供了编写代码库的机会,该代码库不受有色函数的困扰,可以在没有事件循环的情况下对代码进行测试,可以在需要时以同步模式运行代码,并且可以为大量的代码重用。
2Σ↑ (2 Σ ↑)
Code colour is a convenience, not a requirement. And we shouldn’t get too worked up about it, or worry that we’re segmenting our code. Instead, we should focus on using code colour sparingly, because while it adds clarity it also hurts reusability. Making that trade off well is one of the skills of being a programmer, and we should inform our community about that trade off, and then trust them to make the right call.
代码颜色是一种便利,不是必需的。 而且我们不应该对此太费劲,或者担心我们正在分割代码。 相反,我们应该专注于少量使用代码颜色,因为虽然它增加了清晰度,但同时也损害了可重用性。 做出良好的权衡是成为程序员的技能之一,我们应该将这种权衡告知我们的社区,然后相信他们做出正确的选择。
翻译自: https://www.pybloggers.com/2016/07/the-function-colour-myth/
日月神话