【问题标题】:How to do async work in C++ with std::function callback from an ObjC class?如何使用来自 ObjC 类的 std::function 回调在 C++ 中进行异步工作?
【发布时间】:2015-06-16 10:37:56
【问题描述】:

大图

我有一个 C++ 库,它可以执行包括网络在内的异步工作。它有一个特定于达尔文的后端,它使用 Grand Central Dispatch 的 C API 将工作委托给其他线程。现在我想通过一个薄薄的 ObjC++ 层从一个用 Swift 编写的新 iOS 应用程序中使用该库。

我在 OS X 10.10 上使用 Xcode 6.3.2。


在这个最小的示例中,我重新创建了上述架构。问题是当操作通过 std::function 回调返回时,启动异步操作的 ObjC 类实例以某种方式“损坏”。只有当 std::function 被声明为 [&] 而不是 [=] 时才会发生这种情况。我不能使用后者,因为“真正的”C++ 代码不支持它。

调用 ObjC++ 的 Swift 代码如下所示:

class MyViewController : UIViewController {
    var test = ObjectiveTest()

    override func viewDidAppear(animated: Bool) {
        test.testAsyncWork("some data", withHandler: { (result: Int32) -> Void in
            println("Work result: \(result)")
        })
    }
}

如果我注释掉该代码,视图控制器会显示并保持可见,因此它不应该杀死 ObjectiveTest 实例。那就是 ObjC++ 粘合层:

@interface ObjectiveTest () {
    __block Test *test;
    __block int _a;
}
@end

@implementation ObjectiveTest
- (id)init
{
    self = [super init];
    if (self) {
        test = new Test();
        _a = 42;
        if (!test)
            self = nil;
    }
    return self;
}

- (void)deinit
{
    delete test;
}

- (void)testAsyncWork:(NSString *)someData withHandler:(WorkBlock)handler
{
    _a++;
    NSLog(@"_a = %i", _a); // valid
    NSLog(@"handler = %@", handler); // valid
    test->testAsyncWork(someData.UTF8String, [&](int result) {
        _a++;
        NSLog(@"result = %i", result); // Expected: "result = 666" - valid
        NSLog(@"_a = %i", _a); // Expected: "_a = 44" - invalid
        NSLog(@"handler = %@", handler); // invalid, crashes here
    });
}
@end

最后,完成“工作”的 C++ 方法:

void Test::testAsyncWork(std::string someData, std::function<Handler> handler) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        printf("Data received: %s, calling handler on other thread\n", someData.c_str());
        sleep(1); // This is hard work!

        dispatch_async(dispatch_get_main_queue(), ^{
            printf("Hello, main thread here, calling back now!\n");
            handler(666); // into ObjectiveTest
        });
    });
}

观察

正如我所说,如果我将 [=] 用于 std::function,这不会中断。

ObjectiveTest 的 ivar _a 似乎在回调函数中具有随机值,尽管它是用 __block 声明的。程序在尝试访问(打印/调用)块handler 时崩溃,该块回调到 Swift 代码中。 Xcode 显示如下:

handler WorkBlock & error: summary string parsing error 0xbffa50cc
&handler    __block_literal_generic *   0x7c000000  0x0ae08500
__isa   void *  NULL    0x00000000
__flags int 0   0
__reserved  int 2080876705  2080876705
__FuncPtr   void (*)(int)   0x7c000000  0x7c000000
__descriptor    __block_descriptor *    0x7c085b10  0x7c085b10

由此,我得到的印象是 ObjectiveTest 实例在过程中的某个地方中断了,但由于它保存在 MyViewController 中,我看不出这是怎么发生的。我可能错过了其他东西吗?

【问题讨论】:

  • 嗯,使用[&amp;] 进行异步调用是一个非常糟糕的主意吗?它通过引用捕获变量,当您离开当前范围时,它通过引用捕获的变量就消失了。因此,您正在对一组无效的对象进行读取...您要做什么?为什么,确实,您认为[=] 行不通?要回复,请输入带有@yakk 的评论。
  • 嗯,[&amp;] 工作的范围很窄,但这涉及更少的异步,更多的同步。 (你想在特定线程上运行一些任务。所以你发送任务,然后阻塞直到任务完成,阻止你的代码前进)。
  • @yakk 好吧,你是对的。出于某种原因,当我编写新代码时,我一直在想我想要[&amp;],并且我在我们的 C++ 代码中做了同样的事情。我确实理解其中的区别,这一定是我的大脑不喜欢[=] 的外观...如果您将其发布为答案,我会接受。 :)
  • 此外,这里没有通过引用捕获的意义,因为捕获的变量(selfhandler)都没有分配给,所以按值捕获是等效的并且更好。
  • 另外,您永远不要使用__block 声明实例变量。这是没用的,理想情况下应该是编译器错误。 __block 只对局部变量有意义,因为它们是唯一可以“捕获”的变量。

标签: ios c++11 asynchronous objective-c++


【解决方案1】:

[&amp;] 通过引用捕获变量。如果原始变量在任务完成之前过期,则捕获的引用将悬空。

由于异步调用可能旨在异步完成1,因此您基本上可以保证悬空引用。

进行异步调用时,您几乎总是希望按值捕获。您甚至可能想要明确地列出您正在捕获的内容,以便了解您引入的依赖项。 (多线程代码足够难,没有隐藏/隐式依赖)

[&amp;] 捕获的唯一有效用途是创建“访问者”类型的对象以传递给将使用 lambda 的函数,然后在返回之前丢弃它及其所有副本。除了你应该通过价值来捕捉之外的任何东西,或者非常仔细地挑选你通过引用捕捉到的东西,并证明涵盖了生命周期的问题。


1 可能想要[&amp;] 的异步方法的一个示例是“在 UI 泵线程上运行”异步调用,您希望在其中让工作线程暂停进度,直到结果出现从 UI 线程返回。在那里,您将[&amp;],并在您离开当前范围之前阻止返回的std::future(或等效项)。

【讨论】:

  • 此外,捕获的引用可能应该很弱,以避免引用循环和泄漏对象的可能性。请注意,objective-C 块隐式捕获 by const-copy (这导致对引用的计数 Objective-C 对象进行引用) - 块中使用的任何内容。您可能会意外地通过复制在互操作代码中捕获self - 或更糟的是this。我还建议您避免使用 [&amp;][=] capture-all by ref/value notation for C++11 lambdas 并明确列出捕获。使用 std::weak_ptr 将 C++ 对象传递给块。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-09-09
  • 1970-01-01
  • 1970-01-01
  • 2012-01-28
  • 1970-01-01
  • 1970-01-01
  • 2018-08-22
相关资源
最近更新 更多