如何解决 Objective C / async 问题中的竞争条件? 'index 1 beyond bounds for empty array'

How to fix this race condition in Objective C / async problem? 'index 1 beyond bounds for empty array'

您好,我对这个库有疑问: LTSupportAutomotive

我是一名 swift 程序员,所以在 objective c 方面并不是很擅长。

如何解决这个竞争条件?

希望有人能帮帮我。

'*** -[__NSArrayM insertObject:atIndex:]: 索引 1 超出空数组的范围'

-(void)asyncEnqueueInternalCommand:(LTOBD2AdapterInternalCommand*)internalCommand
{
    @synchronized(self) {
        [_commandQueue addObject:internalCommand];
    }
}

源代码:

https://github.com/mickeyl/LTSupportAutomotive/blob/f51b962421f211ee6af5c733f79190117d7cac5e/LTSupportAutomotive/LTOBD2Adapter.m

更新 1:

创建了一个包含第一个修复的分支: https://github.com/Skyb0rg/LTSupportAutomotive/tree/BugfixMemoryManagement

添加额外的 commandQueue 后出现新错误:

在LTBTLEWriteCharacteristicStream.m

-(void)characteristicDidWriteValue
{
    [self.delegate stream:self handleEvent:NSStreamEventHasSpaceAvailable];
}

此功能崩溃: 在当前参数寄存器中找到的选择器名称:delegate

线程 6 崩溃: 0 libobjc.A.dylib 0x00000001bcc19430 objc_retain + 16

1 LTSupportAutomotive 0x00000001093c2a34 -[LTBTLEWriteCharacteristicStream characteristicDidWriteValue] (LTBTLEWriteCharacteristicStream.m:39)

2 LTSupportAutomotive 0x00000001093c2714 -[LTBTLESerialTransporter peripheral:didWriteValueForCharacteristic:error:] (LTBTLESerialTransporter.m:311)

3 CoreBluetooth 0x00000001c35e6ce0 -[CBPeripheral handleAttributeEvent:args:attributeSelector:delegateSelector:delegateFlag:] + 236

4 CoreBluetooth 0x00000001c35e6e40 -[CBPeripheral handleCharacteristicEvent:characteristicSelector:delegateSelector:delegateFlag:] + 128

5 CoreBluetooth 0x00000001c35e24f0 -[CBPeripheral handleMsg:args:] + 352

6 核心蓝牙 0x00000001c35dcbfc -[CBCentralManager handleMsg:args:] + 200

7 CoreBluetooth 0x00000001c35eb770 __30-[CBXpcConnection _handleMsg:]_block_invoke + 56

8 libdispatch.dylib 0x00000001bd4696c8 _dispatch_call_block_and_release + 20

9 libdispatch.dylib 0x00000001bd46a484 _dispatch_client_callout + 12

10 libdispatch.dylib 0x00000001bd411bd0 _dispatch_lane_serial_drain$VARIANT$mp + 588

11 libdispatch.dylib 0x00000001bd41274c _dispatch_lane_invoke$VARIANT$mp + 480

12 libdispatch.dylib 0x00000001bd411a9c _dispatch_lane_serial_drain$VARIANT$mp + 280

13 libdispatch.dylib 0x00000001bd412718 _dispatch_lane_invoke$VARIANT$mp + 428

14 libdispatch.dylib 0x00000001bd41aeb8 _dispatch_workloop_worker_thread + 596

15 libsystem_pthread.dylib 0x00000001bd64d0dc _pthread_wqthread + 308

16 libsystem_pthread.dylib 0x00000001bd64fcec start_wqthread + 0

中的另一个崩溃:LTBTLEReadCharacteristicStream.m

-(void)characteristicDidUpdateValue
{
        NSData* value = _characteristic.value;
        [_buffer appendData:value];
        [self.delegate stream:self handleEvent:NSStreamEventHasBytesAvailable];
}

*** 由于未捕获的异常 'NSInvalidArgumentException' 而终止应用程序,原因:'-[OS_dispatch_data stream:handleEvent:]:无法识别的选择器发送到实例 0x281be1b90'

线程 9 崩溃: 0 libsystem_kernel.dylib 0x00000001bd5c7104 __pthread_kill + 8

1 libsystem_pthread.dylib 0x00000001bd643020 pthread_kill$VARIANT$mp + 376

2 libsystem_c.dylib 0x00000001bd51ed78 中止 + 136

3 VW-R-CLUB会员APP 0x00000001045603ac uncaught_exception_handler + 68

4 CoreFoundation 0x00000001bda321e0 __handleUncaughtException + 688

5 libobjc.A.dylib 0x00000001bcc01e4c _objc_terminate() + 108

6 VW-R-CLUB会员APP 0x0000000104555c4c BITCrashUncaughtCXXTerminateHandler() (BITCrashCXXExceptionHandler.mm:183)

7 libc++abi.dylib 0x00000001bcbf50fc std::__terminate(void (*)()) + 12

8 libc++abi.dylib 0x00000001bcbf5188 std::terminate() + 80

9 libdispatch.dylib 0x00000001bd46a498 _dispatch_client_callout + 32

10 libdispatch.dylib 0x00000001bd411bd0 _dispatch_lane_serial_drain$VARIANT$mp + 588

11 libdispatch.dylib 0x00000001bd41274c _dispatch_lane_invoke$VARIANT$mp + 480

12 libdispatch.dylib 0x00000001bd411a9c _dispatch_lane_serial_drain$VARIANT$mp + 280

13 libdispatch.dylib 0x00000001bd412718 _dispatch_lane_invoke$VARIANT$mp + 428

14 libdispatch.dylib 0x00000001bd41aeb8 _dispatch_workloop_worker_thread + 596

15 libsystem_pthread.dylib 0x00000001bd64d0dc _pthread_wqthread + 308

16 libsystem_pthread.dylib 0x00000001bd64fcec start_wqthread + 0

首先,您显示的代码不是 github 中的代码。您是同步 _commandQueue 的所有用法还是仅同步这一个?如果只有这一个,为什么其他的不同步?

然后,在 github 中我发现了 _commandQueue 的多种用法,其中一些在名为 async... 的方法中,但也有一些不是 async ,例如 cancelPendingCommandsresponseCompleted。您需要找出方法名称中 async 的意图是什么,以及为什么像 cancelPendingCommands 这样的方法不是 async.


更新

所以主要思想似乎是保护串行 _dispatchQueue 内的所有 _commandQueue 访问。在 async... 方法中已经是这种情况:它们是从该队列中调用的:

dispatch_async( _dispatchQueue, ^{
    [self asyncEnqueueInternalCommand:internalCommand];
});

因此您需要确保_commandQueue的每个访问都在此队列中排队,例如将 cancelPendingCommands 更改为

-(void)cancelPendingCommands
{
    dispatch_async( _dispatchQueue, ^{
        // This cancels all but the first command in order to prevent sending a new command while
        // the response to an active command is still pending. OBD2 adapters usually can't cope with
        // that and emit a 'STOPPED' response in that case.
        if ( _hasPendingAnswer )
        {
            NSRange allButTheFirst = NSMakeRange( 1, _commandQueue.count - 1 );
            [_commandQueue removeObjectsInRange:allButTheFirst];
        }
        else
        {
            [_commandQueue removeAllObjects];
        }
    });
}

(或创建一个专用的 asyncCancelPendingCommands 函数,它将在调度块内从 cancelPendingCommands 调用。依此类推。

P.S。如果你需要同步执行,你也可以使用 dispatch_sync 而不是 dispatch_async,但是你必须确保你不会创建死锁。