如何在C++中使一个函数异步化?

28

我希望调用一个异步函数(任务完成后我会提供回调函数)。

我希望在单个线程中完成这个操作。


7
C++没有线程的概念。要得到有用的答案,您需要指定您正在使用的操作系统和库。 - Naveen
7
我还没有找到不支持 pthreads 的 C/C++ 系统。 - Martin York
@Naveen,现在是2023年,C++已经具备了线程的概念。 - Michael IV
2
@MichaelIV 那条评论是在2011年写的。 - Naveen
7个回答

19

使用现代C++或旧版C++和一些boost库可以实现可移植性。Boost和C++11都包含了获取异步值的复杂工具,但如果你只需要一个回调函数,只需启动一个线程并调用它即可。

1998年的C++/boost方法:

#include <iostream>
#include <string>
#include <boost/thread.hpp>
void callback(const std::string& data)
{
    std::cout << "Callback called because: " << data << '\n';
}
void task(int time)
{
    boost::this_thread::sleep(boost::posix_time::seconds(time));
    callback("async task done");
}
int main()
{
    boost::thread bt(task, 1);
    std::cout << "async task launched\n";
    boost::this_thread::sleep(boost::posix_time::seconds(5));
    std::cout << "main done\n";
    bt.join();
}

使用2011年的C++方法(使用需要这个#define的gcc 4.5.2)

#define _GLIBCXX_USE_NANOSLEEP
#include <iostream>
#include <string>
#include <thread>
void callback(const std::string& data)
{
    std::cout << "Callback called because: " << data << '\n';
}
void task(int time)
{
    std::this_thread::sleep_for(std::chrono::seconds(time));
    callback("async task done");
}
int main()
{
    std::thread bt(task, 1);
    std::cout << "async task launched\n";
    std::this_thread::sleep_for(std::chrono::seconds(5));
    std::cout << "main done\n";
    bt.join();
}

14

从C++11开始,纯C++确实有线程的概念,但异步调用函数最简洁的方法是使用C++11 async命令以及future。这最终看起来很像您在pthread中执行相同操作的方式,但它可以在所有操作系统和平台上100%移植。

假设您的函数具有返回值... int = MyFunc(int x, int y)

#include <future>

只需要执行:

// This function is called asynchronously
std::future<int> EventualValue = std::async(std::launch::async, MyFunc, x, y); 

明白了吗?如何知道它何时完成? (屏障。)

最终,执行:

int MyReturnValue = EventualValue.get(); // block until MyFunc is done

请注意,用这种方式执行平行for循环很容易 - 只需创建一个future数组即可。


4
2020年,这是最好的答案。 - Alex
1
你能在 future 上使用 co_await 吗? - jenkas

7
你无法在普通的C++中做到这一点。你需要使用特定于操作系统的机制,并且需要一个能够以允许操作系统执行回调的方式暂停执行的点。例如,对于Windows平台,可以使用QueueUserAPC - 当你使用SleepExWaitForSingleObjectEx时,回调将被执行。

好的,理论上,如果你能弄清楚std::asyncstd::future... 现代编译器肯定支持它们。虽然我确实无法理解它们,所以一个两行代码不到5个编译错误...所以 QueueUserAPC 得加一分。 - Damon
我在想能否使用计时器来完成这个任务,我知道Symbian可以实现。我正在使用Bada框架,他们有一个计时器库。但我不确定如何确切地做到这一点。 - yogesh

4

这需要完成两个步骤。

首先,将函数调用打包以便稍后执行。

其次,安排执行时间。

安排执行时间取决于实现的其他方面。如果你知道“何时完成此任务”,那么你所需要的就是返回并检索“函数调用”,然后调用它。因此,我不确定这是否真的是一个大问题。

第一部分实际上是关于函数对象,甚至函数指针。后者是传统的C回调机制。

对于一个函数对象,你可能会有:

class Callback
{
public:
  virtual void callMe() = 0;
};

你可以根据你的特定问题来实现从中派生并实现这一点。异步事件队列不过是包含回调函数的列表 list<>
std::list<Callback*> asyncQ; // Or shared_ptr or whatever.

我不知道我的函数需要多长时间,所以我需要它是异步的。 - yogesh
@yogesh:在这种情况下,您应该在单独的线程中执行。如果您正在使用单个线程并且有一个阻塞时间较长的函数(> 0.1秒),则会在 UI 中看到它,因为它不会立即响应,或者动画变得卡顿。 - DarkDust
@yogesh:正如DarkDust所说,你真正需要的是线程。也许你应该直接尝试一下。如果你的工作任务可以分解成一系列小任务(即<0.1秒),并且可以单独排队,那么你可以使用上述队列,在GUI空闲时间处理它。 - Keith

4
长答案涉及实现自己的任务调度器,并将您的“函数”封装为一个或多个任务。我不确定您是否需要长答案。它当然不允许您调用某些东西,完全忘记它,然后在完成该事物时得到通知;但是,如果您感到雄心勃勃,它将允许您在某种程度上模拟协程而不需要超出标准C ++ 的范围。
简短的答案是这是不可能的。使用多个线程或多个进程。如果您透露您正在开发的操作系统/平台,我可以给您更具体的信息。

1
使用运行循环(runloop)可以在单个线程中执行异步操作(但您可能需要完整的框架来支持该功能)。苹果的Objective-C/Cocoa框架是一个很好的示例,展示了如何实现这个功能。 - DarkDust
嗯,我不会称Cocoa为任何东西的“好”例子(在我看来!;)),但是没错 - 那是单线程任务调度器的一个很好的例子。 - kqnr
我正在使用Bada,但我不确定每个人都熟悉它。这就是为什么我在询问一种通用方法。 - yogesh

2

我不确定我理解你想要什么,但如果是关于如何使用回调函数:它通过定义函数指针来实现,像这样(未经测试):

// Define callback signature.
typedef void (*DoneCallback) (int reason, char *explanation);

// A method that takes a callback as argument.
void doSomeWorkWithCallback(DoneCallback done)
{
    ...
    if (done) {
       done(1, "Finished");
    }   
}

//////

// A callback
void myCallback(int reason, char *explanation)
{
    printf("Callback called with reason %d: %s", reason, explanation);
}

/////

// Put them together
doSomeWortkWithCallback(myCallback);

2
如果它能够满足异步需求,那就太棒了 - 意味着 doSomeWorkWithCallback(myCallback) 会立即返回,同时与调用者并行执行工作,并在稍后完成工作时调用 myCallback。可惜... - kqnr
是的,这正是我想要的。 - yogesh

0

正如其他人所说,你在普通的C++中技术上是做不到的。

然而,你可以创建一个管理器来接受你的任务并进行时间分片或时间调度;每次函数调用时,管理器使用计时器来测量进程所需的时间量;如果进程所需的时间少于预定时间,并且它认为可以在不超过剩余时间的情况下完成另一个调用,则可以再次调用它;如果函数确实超过了分配的时间,那么下一次更新时该函数将有更少的时间运行。因此,这将涉及创建一个相当复杂的系统来为您处理它。

或者,如果你有一个特定的平台,你可以使用线程或创建另一个进程来处理工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接