15

だから私はメインスレッドでダウンロードを作成しています

NSURLRequest *request = [NSURLRequest requestWithURL:download.URL];
NSURLSessionDownloadTask *downloadTask = [self.downloadSession downloadTaskWithRequest:request];
[downloadTask resume];

ダウンロードに関連付けられた NSManagedContextID を NSMutableDictionary に追加して、後でデリゲート コールバックで取得できるようにします。

[self.downloads setObject:[download objectID] forKey:[NSNumber numberWithInteger:downloadTask.taskIdentifier]];

self.downloadSessionの上記はこのように構成されています

- (NSURLSession *)backgroundSession
{
static NSURLSession *session = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfiguration:@"com.test.backgroundSession"];
    configuration.discretionary = YES;
    session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
});
return session;
}

私の問題は、デリゲート コールバックが別のスレッドで呼び出されているように見えることです。

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{   
    NSManagedObjectID *downloadID = [self.downloads objectForKey:[NSNumber numberWithInteger:downloadTask.taskIdentifier]];

    double progress = (double)totalBytesWritten / (double)totalBytesExpectedToWrite;

    NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:downloadID,@"download",[NSNumber numberWithDouble:progress],@"progress", nil];

    [[NSNotificationCenter defaultCenter] postNotificationName:@"DownloadProgress" object:nil userInfo:userInfo];

} 

そのため、self.downloads にアクセスして正しい objectID を取得すると、実際には、作成されたスレッドとは別のスレッドから NSMutableDictionary にアクセスしています。NSMutableDictionary はスレッドセーフではないと思います。それで、これに対する最善の解決策は何ですか、私はこのようなものを使うことができました

session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]];

セッションを宣言するときに、デリゲート キューを mainQueue に設定します。これにより、すべてのデリゲートがメイン スレッドで呼び出されますが、可能であればすべてのコールバックをバックグラウンド スレッドに保持したいと考えています。

4

2 に答える 2

10

あなたの例では、ディクショナリが通知システムに渡され、操作キュー スレッドによって再び使用されないため、これは問題ではありません。スレッド セーフは、オブジェクトが複数のスレッドから同時にアクセスされる可能性がある場合にのみ問題になります。

dict が iVar になる場合は、次のようにする必要があります。

このように独自のキューを作成します

myQueue = [[NSOperationQueue alloc] init];
// This creates basically a serial queue, since there is just on operation running at any time.
[myQueue setMaxConcurrentOperationCount:1];

次に、このキューでディクショナリへのすべてのアクセスをスケジュールします。たとえば、次のようになります。

[myQueue addOperationWithBlock:^
{
    // Access your dictionary 
}];

もちろん、URLSesson 委任にはこのキューを使用します。

session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:myQueue];

このキューはシリアル キューとして設定されるため、バックグラウンドで dict にアクセスするスレッドは常に 1 つだけです。

dict 情報を使用して何かを計算するときは注意してください。そのキューでもこれを行う必要があります。ただし、たとえばメイン スレッドで UI を更新するために、計算の結果を他のキュー/スレッドに置くことができます。

[myQueue addOperationWithBlock:^
{
    // Calculate with your dictionary
    // Maybe the progress calcualtion
    NSString* progress = [self calculateProgress: iVarDict];
    dispatch_async(dispatch_get_main_queue(), ^
    {
       // use progress to update UI 
    });
}];

システムがスレッドを正しく処理するため、通知を投稿するためにそのパターンを使用する必要はないと思います。しかし、保存するには、これをチェックする必要があります。

于 2013-09-22T11:01:31.447 に答える
0

GCD シリアル キューを使用して、1 つのデリゲートのみが同時に実行されるようにすることができます。

次のように、キューをクラスのインスタンス変数として宣言し、init メソッドで初期化できます。

dispatch_queue_t delegateQueue;

...

delegateQueue = dispatch_queue_create("com.yourcompany.mydelegatequeue", 0x0);

デリゲート メソッドでは、このキューで実行するだけです。

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{   
    dispatch_sync(delegateQueue, ^{
    NSManagedObjectID *downloadID = [self.downloads objectForKey:[NSNumber numberWithInteger:downloadTask.taskIdentifier]];

    double progress = (double)totalBytesWritten / (double)totalBytesExpectedToWrite;

    NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:downloadID,@"download",[NSNumber numberWithDouble:progress],@"progress", nil];

    [[NSNotificationCenter defaultCenter] postNotificationName:@"DownloadProgress" object:nil userInfo:userInfo];
});

} 

この方法では、すべてのデリゲートがそのスレッドで呼び出されますが、一度に self.downloads にアクセスするデリゲートのみが存在し、それらを別々のスレッドに保持できます。

于 2013-09-22T17:54:01.763 に答える