4

我正在使用默认情况下具有非阻塞 I/O 的 Qt 框架来开发一个应用程序,该应用程序在多个网页(在线商店)中导航并在这些页面上执行不同的操作。我正在将特定网页“映射”到我用来浏览此页面的状态机。
这个状态机有这些转换;
Connect, LogIn, Query, LogOut, Disconnect
和这些状态;
Start, Connecting, Connected, LoggingIn, LoggedIn, Querying, QueryDone, LoggingOut, LoggedOut, Disconnecting, Disconnected
从 *ing 到 *ed 状态 ( Connecting->Connected) 的转换是由于在LoadFinished加载当前请求的 url 时从网络对象接收到的异步网络事件。从 *ed 到 *ing 状态 ( Connected->LoggingIn) 的转换是由于我发送的事件。
我希望能够向这台机器发送多个事件(命令)(如 Connect、LogIn、Query("productA")、Query("productB")、LogOut、LogIn、Query("productC")、立即处理它们。我不想阻止等待机器完成处理我发送给它的所有事件。问题是它们必须与上述网络事件交错,通知机器正在下载的 url。没有交错机器就无法推进其状态(并处理我的事件),因为从 *ing 推进到 *ed 只有在接收到网络类型的事件之后才会发生。

如何实现我的设计目标?

编辑

  1. 我正在使用的状态机有自己的事件循环,并且事件没有在其中排队,因此如果机器忙时它们来了,机器可能会错过它们。
  2. 网络 I/O 事件既不会直接发布到状态机,也不会直接发布到我正在使用的事件队列中。它们被发布到我的代码(处理程序)中,我必须处理它们。我可以随心所欲地转发它们,但请记住没有。1.
  3. 看看我对这个问题的回答,我在其中详细描述了我当前的设计。问题是我是否以及如何通过制作它来改进这个设计

    • 更强大
    • 更简单
4

6 回答 6

6

听起来您希望状态机有一个事件队列。将事件排队,开始处理第一个事件,完成后将下一个事件从队列中拉出并开始处理。因此,状态机不是由客户端代码直接驱动,而是由队列驱动。

这意味着任何涉及在下一个转换中使用一个转换的结果的逻辑都必须在机器中。例如,如果“登录完成”页面告诉您下一步该去哪里。如果这不可能,那么事件可能包括机器可以调用的回调,以返回它需要知道的任何内容。

于 2009-08-12T10:39:51.107 回答
2

问这个问题,我已经有了一个工作设计,我不想写关于不向任何方向歪曲答案:) 我将在这个伪答案中描述我的设计是什么。

除了状态机,我还有一个事件队列。我没有将事件直接发布到机器上,而是将它们放入队列中。然而,异步且随时出现的网络事件存在问题。如果队列不为空并且出现网络事件,则我无法将其放入队列中,因为在处理队列中已经存在的事件之前机器将等待它。并且机器将永远等待,因为这个网络事件在之前放入队列中的所有事件之后等待。
为了克服这个问题,我有两种类型的消息;正常和优先的。正常的是我发的,优先的都是网络的。当我收到网络事件时,我不会将其放入队列中,而是将其直接发送到机器。这样,它可以在从事件队列中拉出下一个事件之前完成其当前任务并进入下一个状态。
之所以这样设计,是因为我的事件和网络事件恰好是 1:1 交错的。因此,当机器等待网络事件时,它并不忙于做任何事情(因此它已准备好接受它并且不会错过它),反之亦然 - 当机器等待我的任务时,它只是在等待我的任务而不是另一个网络一。

我问了这个问题,希望能得到比现在更简单的设计。

于 2009-08-12T11:48:45.630 回答
1

严格来说,你不能。因为你只有状态“正在连接”,所以你不知道之后是否需要顶部登录。您必须引入一个状态“ConnectingWithIntentToLogin”来表示来自 Start 状态的“Connect, then Login”事件的结果。

自然,“Connecting”和“ConnectingWithIntentToLogin”状态之间会有很多重叠。这最容易通过支持状态层次结构的状态机架构来实现。

- - 编辑 - -

阅读您后来的反应,现在很清楚您的实际问题是什么。

显然,您确实需要额外的状态,无论是在 FSM 中根深蒂固的状态,还是在单独队列中的外部状态。让我们按照您喜欢的模型,在队列中添加额外的事件。这里的问题是您想知道如何将那些排队的事件与实时事件“交错”。您不会 - 进入特定状态时会主动提取队列中的事件。在您的情况下,这些将是“*ed”状态,例如“已连接”。只有当队列为空时,您才会保持“已连接”状态。

于 2009-08-12T10:37:53.777 回答
0

如果您不想阻止,则意味着您不关心网络回复。另一方面,如果您对回复感兴趣,则必须阻止等待它们。否则尝试设计您的 FSM 将很快导致您的自动机的大小达到无穷大。

于 2009-08-12T11:02:48.530 回答
0

如何将状态机移动到不同的线程,即 QThread。我会在状态机中实现一个输入队列,这样我就可以发送非阻塞查询和一个输出队列来读取查询结果。如果查询结果到达,您甚至可以通过 connect(...) 在主线程中回调一个开槽函数,Qt 在这方面是线程安全的。

这样,您的状态机可以在需要时阻塞,而不会阻塞您的主程序。

于 2009-08-12T14:24:46.747 回答
0

听起来你只想在后台做一个阻塞 I/O 的列表。

所以有一个线程执行:

while( !commands.empty() )
{
  command = command.pop_back();
  switch( command )
  {
  Connect: 
    DoBlockingConnect();
    break;
  ...
  }
}
NotifySenderDone();
于 2009-08-12T14:26:54.543 回答