4

我目前正在开发一个需要通过网络控制收集设备的服务器应用程序。正因为如此,我们需要做很多并行编程。随着时间的推移,我了解到处理实体(线程/进程/应用程序)之间的通信有三种方法。遗憾的是,这三种方法都有其缺点

A)您可以发出同步请求(同步函数调用)。在这种情况下,调用者会一直等待,直到函数处理完毕并收到响应。例如:

const bool convertedSuccessfully = Sync_ConvertMovie(params);

问题是调用者处于空闲状态。有时这不是一个选择。例如,如果调用是由用户界面线程进行的,那么在响应到达之前,应用程序似乎一直处于阻塞状态,这可能需要很长时间。

B)您可以发出异步请求并等待回调。客户端代码可以继续做任何需要做的事情。

Async_ConvertMovie(params, TheFunctionToCallWhenTheResponseArrives);

这种解决方案的一个很大的缺点是回调函数必须在单独的线程中运行。现在的问题是很难将响应返回给调用者。例如,您单击了对话框中的一个按钮,该按钮异步调用了一个服务,但是当回调到达时,对话框已经关闭了很长时间。

void TheFunctionToCallWhenTheResponseArrives()
{
    //Difficulty 1: how to get to the dialog instance?
    //Difficulty 2: how to guarantee in a thread-safe manner that
    //              the dialog instance is still valid?
}

这本身并不是什么大问题。但是,当您要拨打多个此类电话,并且它们都依赖于前一个电话的响应时,根据我的经验,这将变得非常复杂

C)我看到的最后一个选项是发出异步请求并继续轮询,直到响应到达。在 has-the-response-arrived-yet 检查之间,您可以做一些有用的事情。这是我所知道的解决需要进行一系列异步函数调用的情况的最佳解决方案。这是因为它具有很大的优势,即在响应到达时您仍然拥有整个调用者上下文。此外,调用的逻辑顺序仍然相当清晰。例如:

const CallHandle c1 = Sync_ConvertMovie(sourceFile, destFile);
while(!c1.ResponseHasArrived())
{
    //... do something in the meanwhile
}
if (!c1.IsSuccessful())
    return;

const CallHandle c2 = Sync_CopyFile(destFile, otherLocation);
while(!c1.ResponseHasArrived())
{
    //... do something in the meanwhile
}
if (c1.IsSuccessful())
    //show a success dialog

第三种解决方案的问题是您不能从调用者的函数返回。如果您想要在两者之间做的工作与您异步完成的工作完全无关,这将使其不合适。很长一段时间以来,我想知道是否还有其他异步调用函数的可能性,一种没有上面列出的选项的缺点。有没有人有一个想法,也许是一些聪明的把戏?

注意:给出的示例是类 C++ 的伪代码。但是,我认为这个问题同样适用于 C# 和 Java,可能还有很多其他语言。

4

3 回答 3

3

您可以考虑一个显式的“事件循环”或“消息循环”,这与经典方法没有太大区别,例如select异步网络任务的循环或窗口系统的消息循环。到达的事件可能会在适当的时候被分派给回调,例如在您的示例 B 中,但在某些情况下它们也可能被不同地跟踪,例如在有限状态机中导致事务。毕竟,FSM 是一种很好的方式来管理需要许多步骤的协议中交互的复杂性!

将这些考虑系统化的一种方法是从Reactor设计模式开始。

如果您有 C++ 背景, Schmidt 的ACE工作是解决这些问题的一个很好的起点;Twisted也很值得,来自 Python 背景;而且我敢肯定,正如您所说,“许多其他语言”存在类似的框架和白皮书集(我给出的 Wikipedia URL 确实指向了除 ACE 和 Twisted 之外的其他语言的 Reactor 实现)。

于 2009-06-25T15:51:41.287 回答
2

我倾向于选择 B,但不是来回调用,而是在单独的线程上进行整个处理,包括后续处理。主线程可以同时更新GUI,或者主动等待线程完成(即显示一个带有进度条的对话框),或者让它在后台做它的事情并在它完成时接收通知。到目前为止没有复杂性问题,因为从处理线程的角度来看,整个处理实际上是同步的。从 GUI 的角度来看,它是异步的。

除此之外,在 .NET 中切换到 GUI 线程是没有问题的。BackgroundWorker 类和 ThreadPool 也使这变得简单(如果我没记错的话,我使用了 ThreadPool)。例如,在 Qt 中,要继续使用 C++,也很容易。

我在上一个主要应用程序中使用了这种方法,对此我感到非常满意。

于 2009-06-25T15:49:50.083 回答
1

就像 Alex 所说的,看看 Doug Schmidt 在软件架构模式中记录的 Proactor 和 Reactor。

在 ACE 中有针对不同平台的具体实现。

于 2009-06-25T15:58:26.373 回答