我有一个应用程序,可以在几个线程中从服务器下载一些文件。问题在于它给CPU带来了沉重的负担(达到80%)。有什么办法可以让它变得更好?我在Windows上使用C#制作了类似的应用程序,并且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
答案 0 :(得分:2)
你不应该自己创建线程。为此,可以直接使用NSOperationQueue甚至GCD等专用API。他们更了解硬件限制,虚拟核心等,并支持优先级设置。
您也不应该使用+sendSynchronousRequest:
。在charith建议的调度调用中包装-downloadChunk
方法无助于提高性能,因为+sendSynchronousRequest:
会阻塞线程直到新数据进入并强制GCD生成新线程。
使用委托回调使用NSURLConnection
的异步API。您还可以将NSURLConnection代码包装在NSOperation
子类中,并使用NSOperationQueue
来管理下载:Using NSURLConnections
如果您不想自己编写NSOperation
子类,也可以使用AFNetworking之类的第三方框架。
答案 1 :(得分:0)
尝试使用GCD块和全局队列。这是苹果推荐的并发方式:
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(globalQueue, ^{
[self downloadChunk:objDownload];
});