【问题标题】:Synchronize Asynchronous tasks in Objective-C在 Objective-C 中同步异步任务
【发布时间】:2014-10-28 19:47:31
【问题描述】:

我正在尝试使用 GCDAsyncSocket 向我的 iDevice 客户端发送一些图像文件(大约 100MB)。

我想同步向客户端发送数据包。我的意思是在向第一个客户端发送 100MB 数据之后迭代到下一个客户端。但是由于 GCDAsyncSocket 的异步性质,我不知道如何序列化这些数据包发送。

我不能使用信号量,因为在发送图像之前,我会与每个客户协商以了解我应该发送哪些图像,然后尝试发送这些图像。而且我找不到一种巧妙的方法来等待信号量并发出信号。

- (void)sendImagesToTheClients:clients
{
    ...
    //negotiating with client to know which images should sent
    ...

    for(Client* client in clients)
    {
       packet = [packet addImages: images];
       [self sendPacket:packet toClient:client];
    }
}

- (void)sendPacket:packet toClient:client
{
 // Initialize Buffer
    NSMutableData *buffer = [[NSMutableData alloc] init];
    NSData *bufferData = [NSKeyedArchiver archivedDataWithRootObject:packet];

    uint64_t headerLength = [bufferData length];
    [buffer appendBytes:&headerLength length:sizeof(uint64_t)];
    [buffer appendBytes:[bufferData bytes] length:[bufferData length]];

    // Write Buffer
    [client.socket writeData:buffer withTimeout:-1.0 tag:0];
}

这就是 AsyncSocket 写入数据的工作原理:

- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag
{
    if ([data length] == 0) return;

    GCDAsyncWritePacket *packet = [[GCDAsyncWritePacket alloc] initWithData:data timeout:timeout tag:tag];

    dispatch_async(socketQueue, ^{ @autoreleasepool {

        LogTrace();

        if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites))
        {
            [writeQueue addObject:packet];
            [self maybeDequeueWrite];
        }
    }});

    // Do not rely on the block being run in order to release the packet,
    // as the queue might get released without the block completing.
}

那么我怎样才能同步这个任务呢?

更新

对于套接字连接,我使用GCDAsyncSocket,它大量使用委托进行事件通知。(GCDAsyncSocket.h 和 GCDAsyncSocket.m)(没有使用 completionHandler 的方法)。

我编写了一个名为 TCPClient 的类,它处理套接字连接和数据包发送,并将其设置为初始化套接字的委托。 写入数据包后,将调用委托方法- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag。这只告诉我一些数据已被写入。在这里我无法根据书面数据决定调用dispatch_group_leave。所以依赖委托方法是没用的。

我在 GCDAsyncSocket.h 和 .m 文件中修改了 [client.socket writeData:buffer withTimeout:-1.0 tag:0] 以接受 completionBlock:[client.socket writeData:buffer withTimeout:-1.0 tag:0 completionBlock:completionBlock] 使用这种方法可以帮助我解决同步异步任务。

// perform your async task
dispatch_async(self.socketQueue, ^{
    [self sendPacket:packet toClient:client withCompletion:^(BOOL finished, NSError *error)
     {
         if (finished) {
             NSLog(@"images file sending finished");
             //leave the group when you're done
             dispatch_group_leave(group);
         }
         else if (!finished && error)
         {
             NSLog(@"images file sending FAILED");
         }
     }];

但问题是更新 GCDAsyncsocket 后,我​​的代码可能会中断。 在这里,我正在寻找一种巧妙的方法来将完成处理程序添加到 GCDAsyncsocket 而不直接修改它。比如围绕它创建一个包装器或者使用objective-c运行时的特性。 你有什么想法吗?

【问题讨论】:

    标签: objective-c asynchronous grand-central-dispatch synchronous gcdasyncsocket


    【解决方案1】:

    您可以使用调度组来完成此操作。对于带有完成块的异步任务:

    //create & enter the group
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_enter(group);
    
    // perform your async task
    dispatch_async(socketQueue, ^{
    
        //leave the group when you're done
        dispatch_group_leave(group);
    });
    
    // wait for the group to complete 
    // (you can use DISPATCH_TIME_FOREVER to wait forever)
    long status = dispatch_group_wait(group,
                        dispatch_time(DISPATCH_TIME_NOW,NSEC_PER_SEC * COMMAND_TIMEOUT));
    
    // check to see if it timed out, or completed
    if (status != 0) {
        // timed out
    }
    

    对于有委托的任务:

    @property (nonatomic) dispatch_group_t group;
    
    -(BOOL)doWorkSynchronously {
    
        self.group = dispatch_group_create();
        dispatch_group_enter(self.group);
    
        [object doAsyncWorkWithDelegate:self];
    
        long status = dispatch_group_wait(self.group,
                            dispatch_time(DISPATCH_TIME_NOW,NSEC_PER_SEC * COMMAND_TIMEOUT));
    
        // A
        if (status != 0) {
            // timed out
            return NO
        }
    
        return YES;
    }
    
    -(void)asyncWorkCompleted {}
    
        // after this call, control should jump back to point A in the doWorkSynchronously method
        dispatch_group_leave(self.group);
    }
    

    【讨论】:

    • - (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tagGCDAyncSocket.m 中的一个方法,我不应该修改它。我可以将我的 socketQueue 传递给GCDAsynSocket。我应该创建另一个 dispatch_async 并将sendPacket:client: 放入其中然后调用`dispatch_group_leave(group);`?因为我做到了,但没有任何改变。仍然是异步的。
    • GCDAsyncSocket 通过委托回调通知我完成didWriteDataWithTag: 问题出在此方法不会根据该数据向我发送书面数据(如果它们是图像)我打电话给@987654330 @。 GCDAsyncSocket 不会通过 completionHandler 通知我。
    • 请更新您的问题以反映该库的委托性质。我会更新我的答案,您可以将 dispatch_group_leave 放在委托方法中,它应该仍然可以工作。
    • 对于 Swift 实现:stackoverflow.com/a/41120715/1033581
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-30
    • 1970-01-01
    • 2020-11-26
    • 1970-01-01
    • 1970-01-01
    • 2017-01-11
    相关资源
    最近更新 更多