如何保持异步并行程序代码的可管理性(例如在C ++中)

时间:2009-06-25 15:32:39

标签: c++ multithreading asynchronous async-workflow

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

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)我看到的最后一个选项是在响应到达之前发出异步请求并保持轮询。在响应已到达但尚未检查之间,您可以做一些有用的事情。这是我所知道的解决这种情况的最佳解决方案,其中有一系列异步函数调用。这是因为它具有很大的优势,即当响应到达时,您仍然拥有整个调用者上下文。此外,呼叫的逻辑顺序仍然相当清楚。例如:

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,可能还有很多其他语言。

3 个答案:

答案 0 :(得分:3)

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

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

如果你来自C ++背景,施密特的ACE工作是这些问题的一个很好的起点;从Python背景来看,Twisted也非常值得;我确信存在类似的框架和白皮书,正如你所说,“很多其他语言”(我给的维基百科网址确实指出了其他语言的Reactor实现,除了ACE和Twisted)。

答案 1 :(得分:2)

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

除此之外,在.NET中切换到GUI线程没有问题。 BackgroundWorker类和ThreadPool也很容易(如果我没记错的话,我使用了ThreadPool)。例如,在Qt中,为了保持C ++,它也很容易。

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

答案 2 :(得分:1)

像亚历克斯所说,看看道格施密特在软件架构模式中记录的Proactor和Reactor。

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