1

我有一个应用程序可以在几个线程中从服务器下载一些文件。问题是它给 CPU 带来了沉重的负载(达到 80%)。可以做些什么来让它变得更好?我用 C# 在 Windows 上制作了类似的应用程序,cpu 使用率从未超过 5%。

编辑:在得到以下一些建议后,此代码已更改。现在的问题是,当我设置 [queue setMaxConcurrentOperationCount:6] 时,下载永远不会达到 100%。如果我将异步 NSURLConnection 改回 sendSynchronous 调用它可以工作,当我将上面的 OperationCount 更改为 1 时,它也可以工作。

这就是我将 NSOperations 添加到队列中的方式(可能很大,比如 800)。

int chunkId = 0;
for (DownloadFile *downloadFile in [download filesInTheDownload])
{
    chunkId = 0;
    for (DownloadChunk *downloadChunk in [downloadFile chunksInTheFile])
    {
        DownloadChunkOperation *operation = [[DownloadChunkOperation alloc]  initWithDownloadObject:download
      downloadFile:downloadFile                                                                                                    downloadChunk:downloadChunk                                                                                                           andChunkId:chunkId];
    [queue addOperation:operation];
    chunkId++;
    }
}


#import "DownloadChunkOperation.h"
#import "Download.h"
#import "DownloadFile.h"
#import "DownloadChunk.h"

@interface DownloadChunkOperation()
@property(assign) BOOL isExecuting;
@property(assign) BOOL isFinished;
@end

@implementation DownloadChunkOperation

@synthesize download = _download;
@synthesize downloadFile = _downloadFile;
@synthesize downloadChunk = _downloadChunk;

@synthesize isFinished = _isFinished;
@synthesize isExecuting = _isExecuting;

- (id) initWithDownloadObject:(Download *)download downloadFile:(DownloadFile *)downloadFile downloadChunk:(DownloadChunk *)downloadChunk andChunkId:(uint32_t)chunkId
{
    self = [super init];

    if (self) {
        self.download = download;
        self.downloadFile = downloadFile;
        self.downloadChunk = downloadChunk;
        self.chunkId = chunkId;
    }

    return self;
}

- (void) start
{
    if ([self isCancelled]) {
        [self setIsFinished:YES];
        [self setIsExecuting:NO];
        return;
    }

    [self setIsExecuting:YES];
    [self setIsFinished:NO];
    [self.downloadChunk setChunkState:cDownloading];

    downloadPath = [[NSString stringWithFormat:@"%@/%@", [self.download downloadFolder], [self.download escapedTitle]] stringByExpandingTildeInPath];

    NSURL *fileURL = [[NSURL alloc] initWithString:[self.downloadFile filePath]];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:fileURL];
    NSString *range = [NSString stringWithFormat:@"bytes=%lli-%lli", [self.downloadChunk startingByte], [self.downloadChunk endingByte]];
    [request setValue:range forHTTPHeaderField:@"Range"];
    connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
    // IMPORTANT! The next line is what keeps the NSOperation alive for the during of the NSURLConnection!
    [connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [connection start];

    if (connection) {
        NSLog(@"connection established!");
        do {
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        } while (!self.isFinished);
    } else {
        NSLog(@"couldn't establish connection for: %@", fileURL);
    }
}

- (BOOL) isConcurrent
{
    return YES;
}

- (void) connection:(NSURLConnection *)_connection didReceiveResponse:(NSURLResponse *)response
{
    receivedData = [[NSMutableData alloc] init];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    // Not cancelled, receive data.
    if (![self isCancelled]) {
        [receivedData appendData:data];
        self.download.downloadedBytes += [data length];
        return;
    }

    // Cancelled, tear down connection.
    [self setIsExecuting:NO];
    [self setIsFinished:YES];
    [self.downloadChunk setChunkState:cConnecting];
    [self->connection cancel];
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    [self setIsExecuting:NO];
    [self setIsFinished:YES];
    NSLog(@"Connection failed! Error - %@ %@",
          [error localizedDescription],
          [[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    NSString *chunkPath = [downloadPath stringByAppendingFormat:@"/%@.%i", [self.downloadFile fileName], self.chunkId];
    NSError *saveError = nil;
    [receivedData writeToFile:chunkPath options:NSDataWritingAtomic error:&saveError];
    if (saveError != nil) {
        NSLog(@"Download save failed! Error: %@", [saveError description]);
    }
    else {
        NSLog(@"file has been saved!: %@", chunkPath);
    }
    [self setIsExecuting:NO];
    [self setIsFinished:YES];
    [self.downloadChunk setChunkState:cFinished];

    if ([self.download downloadedBytes] == [self.download size])
        [[NSNotificationCenter defaultCenter] postNotificationName:@"downloadFinished" object:self.download];
}

@end
4

2 回答 2

2

您不应该自己创建线程。为此目的直接使用像 NSOperationQueue 甚至 GCD 这样的专用 API。他们更了解硬件限制、虚拟内核等,并支持优先级设置。

你也不应该使用+sendSynchronousRequest:。按照 charith 的建议将您的-downloadChunk方法包装在调度调用中不会帮助您提高性能,因为会+sendSynchronousRequest:阻塞线程,直到新数据进入并强制 GCD 产生新线程。

NSURLConnection使用委托回调的异步 API 。您还可以将 NSURLConnection 代码包装在NSOperation子类中并用于NSOperationQueue管理下载:Using NSURLConnections

如果您不想NSOperation自己编写子类,也可以使用AFNetworking之类的 3rd 方框架。

于 2012-07-31T12:58:34.757 回答
0

尝试使用 GCD 块和全局队列。这是苹果现在推荐的并发方式:

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(globalQueue, ^{

        [self downloadChunk:objDownload];
    });
于 2012-07-31T12:54:17.410 回答