我想从外部服务中读取和处理一些数据。我问服务是否有任何数据,如果有什么东西返回我处理它并再次询问(因此数据可以在它可用时立即处理),否则我等待数据可用的通知。这可以写成无限循环:
def loop(self):
while True:
data = yield self.get_data_nonblocking()
if data is not None:
yield self.process_data(data)
else:
yield self.data_available
def on_data_available(self):
self.data_available.fire()
如何data_available
在这里实施?它可能是延迟但延迟不能重置,只能重新创建。有更好的选择吗?
这个循环可以集成到Twisted事件循环中吗?我可以在on_data_available
中读取和处理数据并编写一些代码而不是循环检查get_data_nonblocking
但我觉得我需要一些锁以确保数据以相同的顺序处理它到了(上面的代码强制执行它,因为它是它处理的唯一地方)。这根本不是一个好主意吗?
答案 0 :(得分:3)
考虑TCP连接的情况。 TCP连接的接收缓冲区可以包含数据,也可以不包含数据。您可以通过使用非阻塞套接字API来获取该数据,或者什么都不做,而不会阻塞:
data = socket.recv(1024)
if data:
self.process_data(data)
您可以使用select()
(或任何基本等效的API)等待数据可用:
socket.setblocking(False)
while True:
data = socket.recv(1024)
if data:
self.process_data(data)
else:
select([socket], [], [])
其中,只有select()
特别 Twisted-unndlyndly(虽然Twisted成语肯定不会自己进行socket.recv
次调用)。您可以将select
调用替换为适合扭曲的版本(使用Protocol
方法实施dataReceived
来触发Deferred
- 有点像on_data_available
方法 - 抛出一些产量并使整个事件成为inlineCallbacks
生成器。
但是,虽然这是从TCP连接获取数据的一种方式,但这并不是Twisted鼓励您使用的API。相反,API是:
class SomeProtocol(Protocol):
def dataReceived(self, data):
# Your logic here
我不知道你的情况有多大不同。如果你做了类似的事情而不是你写的循环,那该怎么办:
class YourDataProcessor(object):
def process_data(self, data):
# Your logic here
class SomeDataGetter(object):
def __init__(self, processor):
self.processor = processor
def on_available_data(self):
data = self.get_data_nonblocking()
if data is not None:
self.processor.process_data(data)
现在根本没有延迟(除了可能在任何实现on_available_data
或get_data_nonblocking
中,但我看不到该代码)。
如果你粗略地保留这个,你可以保证按顺序执行,因为Twisted是单线程的(除了在几个非常清楚标记的地方),并且在单线程程序中,之前的调用是process_data
必须在以后调用process_data
之前完成{当然,process_data
重新调用自身的情况除外 - 但这是另一个故事。)
如果你将其切换回使用inlineCallbacks
(或任何等效的“coroutine”风味饮料混合物),那么你可能会引入无序执行的可能性。
例如,如果get_data_nonblocking
返回Deferred
并且你写了这样的内容:
@inlineCallbacks
def on_available_data(self):
data = yield self.get_data_nonblocking()
if data is not None:
self.processor.process_data(data)
然后您已更改on_available_data
以表示在调用get_data_nonblocking
时允许上下文切换。在这种情况下,根据您对get_data_nonblocking
和on_available_data
的实施情况,完全有可能:
on_available_data
被称为get_data_nonblocking
被调用并返回Deferred
on_available_data
告诉执行切换到另一个上下文(通过yield
/ inlineCallbacks
)on_available_data
get_data_nonblocking
并返回Deferred
(也许是同一个!也许是新的!取决于它是如何实现的)on_available_data
的第二次调用告诉执行切换到另一个上下文(同样的原因)Deferred
的第二次调用返回的get_data_nonblocking
被触发。on_available_data
框架process_data
调用返回的任何数据调用get_data_nonblocking
process_data
调用返回的任何数据再次调用get_data_nonblocking
现在也许您已经无序处理了数据 - 再次,这取决于系统其他部分的更多细节。
如果是这样,您可以随时重新下订单。对此有很多不同的可行方法。 Twisted本身没有任何明确支持此操作的API,因此解决方案涉及编写一些新代码。这是一个方法的一个想法(未经测试) - 一个类似队列的类,它知道对象序列号:
class SequencedQueue(object):
"""
A queue-like type which guarantees objects come out of the queue in the order
defined by a sequence number associated with the objects when they are put into
the queue.
Application code manages sequence number assignment so that sequence numbers don't
have to have the same order as `put` calls on this type.
"""
def __init__(self):
# The sequence number of the object that should be given out
# by the next call to `get`
self._next_sequence = 0
# The sequence number of the next result that needs to be provided.
self._next_result = 0
# A holding area for objects past _next_sequence
self._queue = {}
# A holding area
self._waiting =
def put(self, sequence, object):
"""
Put an object into the queue at a particular point in the sequence.
"""
if sequence < self._next_sequence:
# Programming error. The sequence number
# of the object being put has already been used.
raise ...
self._queue[sequence] = object
self._check_waiters()
def get(self):
"""
Get an object from the queue which has the next sequence number
following whatever was previously gotten.
"""
result = self._waiters[self._next_sequence] = Deferred()
self._next_sequence += 1
self._check_waiters()
return result
def _check_waiters(self):
"""
Find any Deferreds previously given out by get calls which can now be given
their results and give them to them.
"""
while True:
seq = self._next_result
if seq in self._queue and seq in self._waiting:
self._next_result += 1
# XXX Probably a re-entrancy bug here. If a callback calls back in to
# put then this loop might run recursively
self._waiting.pop(seq).callback(self._queue.pop(seq))
else:
break
预期的行为(模块化我意外添加的任何错误)类似于:
q = SequencedQueue()
d1 = q.get()
d2 = q.get()
# Nothing in particular happens
q.put(1, "second result")
# d1 fires with "first result" and afterwards d2 fires with "second result"
q.put(0, "first result")
使用此功能,只需确保按照您希望分派数据的顺序分配序列号,而不是按照实际显示的顺序分配序列号。例如:
@inlineCallbacks
def on_available_data(self):
sequence = self._process_order
data = yield self.get_data_nonblocking()
if data is not None:
self._process_order += 1
self.sequenced_queue.put(sequence, data)
在其他地方,某些代码可以使用类似的队列类型:
@inlineCallbacks
def queue_consumer(self):
while True:
yield self.process_data(yield self.sequenced_queue.get())