如何停止或取消调度块的执行

问题描述:

我想同时执行多个调度块。所以当我运行第二个调度块的同时,当任何调度块正在进行时,我想停止执行先前的调度块。如何停止或取消调度块的执行

我使用这下面的代码:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
     NSData *data = [NSData dataWithContentsOfURL:item.URL]; 
     dispatch_async(dispatch_get_main_queue(), ^(void){ 
     NSError *error = nil; 
     self.player =[[AVAudioPlayer alloc] initWithData:data fileTypeHint:AVFileTypeMPEGLayer3 error:&error]; 
      NSLog(@"%@",error); 
     }); 
    }); 

而且我也试过这个下面的代码。但是,如果我用这个下面的代码就可以取消之前的块,但我的应用程序进入提前挂

//Use NSOperationQueue 
    myQueue = [NSOperationQueue mainQueue]; 
    [myQueue addOperationWithBlock:^{ 

     // Background work 
     NSData *data = [NSData dataWithContentsOfURL:item.URL]; 
     [[NSOperationQueue mainQueue] addOperationWithBlock:^{ 
      // Main thread work (UI usually) 
      NSError *error = nil; 
      self.player =[[AVAudioPlayer alloc] initWithData:data fileTypeHint:AVFileTypeMPEGLayer3 error:&error]; 
      NSLog(@"%@",error); 
     }]; 
    }]; 

感谢,

+0

GCD中的调度块无法取消,但NSOperationQueue中的块可以取消。 – EricXuan

+0

@EricXuan:是的,我知道'NSOperationQueue'可以取消但是当我使用'NSOperationQueue'时,我的申请被卡住了 –

+3

@EricXuan - 其实你现在可以在GCD中取消。 (它不像'NSOperation'中的取消逻辑那样优雅。)问题是'dataWithContentsOfURL',因为这是不可取消的。他应该使用'NSURLSession',这是可取消的。他应该完全失去GCD和/或NSOperation,或者如果他想保留它,他应该使用可取消的自定义异步NSOperation子类,并使用cancel方法取消该任务(尽管这是一个比只是更加丰富的解决方案'NSURLSession'本身,这也是一个更多的工作)。 – Rob

没有派遣队列或操作队列都是必须的。

您只需要能够使用NSURLSession开始异步下载会话,并且下载成功后,启动异步AVAudioPlayer。而且由于这些是异步任务,您可以分别使用cancelstop

下面是一个简单的例子:

@class Song; 

@protocol SongDelegate <NSObject> 

- (void)song:(Song *)song didFinishPlayingSuccessfully:(BOOL)flag; 
- (void)song:(Song *)song didFinishDownloadWithError:(NSError *)error; 

@end 

@interface Song: NSObject <AVAudioPlayerDelegate> 
@property (nonatomic, strong) NSURL *url; 
@property (nonatomic, weak) NSURLSessionTask *downloadTask; 
@property (nonatomic, strong) NSURL *localURL; 
@property (nonatomic, strong) AVAudioPlayer *player; 
@property (nonatomic, weak) id<SongDelegate> delegate; 
@end 

@implementation Song 

+ (instancetype)songWithURL:(NSURL *)url delegate:(id<SongDelegate>)delegate { 
    Song *song = [[Song alloc] init]; 
    song.url = url; 
    song.delegate = delegate; 
    return song; 
} 

- (void)downloadAndPlay { 
    self.downloadTask = [[NSURLSession sharedSession] downloadTaskWithURL:self.url completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) { 
     dispatch_async(dispatch_get_main_queue(), ^{ 
      [self.delegate song:self didFinishDownloadWithError:error]; 
     }); 
     NSURL *documentsURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:false error:&error]; 
     NSAssert(documentsURL, @"URLForDirectory failed: %@", error); 
     NSURL *fileURL = [documentsURL URLByAppendingPathComponent:self.url.lastPathComponent]; 
     NSError *moveError; 
     BOOL success = [[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&moveError]; 
     NSAssert(success, moveError.localizedDescription); 

     // note, the only reason we dispatch the following is that this completion handler runs on background queue and we want to update properties and start the player from the main queue 

     dispatch_async(dispatch_get_main_queue(), ^{ 
      self.localURL = fileURL; 
      NSError *playError; 
      self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:&playError]; 
      self.player.delegate = self; 
      [self.player play]; 
      NSAssert(playError == nil, playError.localizedDescription); 
     }); 
    }]; 
    [self.downloadTask resume]; 
} 

- (void)cancel { 
    [self.downloadTask cancel]; // if download still in progress, stop it 
    [self.player stop];   // if playing, stop it 
} 

- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag { 
    self.player = nil; 
    [self.delegate song:self didFinishPlayingSuccessfully:flag]; 
} 

@end 

所以你可以看到,downloadAndPlay启动异步下载,当这样做,开始轨道的同步播放。如果正在进行,cancel方法会取消下载,如果正在进行则停止播放。

然后你就可以使用它像这样:

@interface ViewController() <SongDelegate> 

@property (nonatomic, strong) Song *song; 

@end 

@implementation ViewController 

- (void)viewDidLoad { 
    [super viewDidLoad]; 

    self.song = [Song songWithURL:[NSURL URLWithString:@"http://dl.last.fm/static/1464677535/131211148/70b3b5a9d048c7939d5bb9ec87a2c5d58d6ee528828f5c6a5b7b1eddd69f4553/Death+Grips+-+Get+Got.mp3"] delegate:self]; 

    // Do any additional setup after loading the view, typically from a nib. 
} 

- (IBAction)didTapPlayButton:(id)sender { 
    [self.song downloadAndPlay]; 
} 

- (IBAction)didTapStopButton:(id)sender { 
    [self.song cancel]; 
} 

- (void)song:(Song *)song didFinishPlayingSuccessfully:(BOOL)flag { 
    NSLog(@"did finish playing %@", flag ? @"successfully" : @"unsuccessfully"); 
} 

- (void)song:(Song *)song didFinishDownloadWithError:(NSError *)error { 
    NSLog(@"did finish download with error %@", error.localizedDescription); 
} 
@end 

现在,很显然,这是一个简单的实现(你真的不想要做NSAssert,如果您有任何错误,而是处理它优雅,您想要处理一系列Song对象,您可能希望从播放中分离下载,以便您可以在歌曲1播放时开始下载歌曲2等),但它说明了取消下载或播放一首歌曲,它们都是异步任务,所以不需要调度队列或操作队列。你可以做到这一点,如果你想要看中,但这是一个更高级的话题。

顺便说一句,NSURLSession是相当严密的关于禁止,因为它们所构成的安全风险非HTTPS请求,但您可以编辑info.plist(右键单击它,然后说“打开为” - “源代码”)然后输入如下内容:

<key>NSAppTransportSecurity</key> 
<dict> 
    <key>NSExceptionDomains</key> 
    <dict> 
     <key>dl.last.fm</key> 
     <dict> 
      <!--Include to allow subdomains--> 
      <key>NSIncludesSubdomains</key> 
      <true/> 
      <!--Include to allow HTTP requests--> 
      <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key> 
      <true/> 
      <!--Include to specify minimum TLS version--> 
      <key>NSTemporaryExceptionMinimumTLSVersion</key> 
      <string>TLSv1.1</string> 
     </dict> 
    </dict> 
</dict> 
+0

谢谢,让我试试你的解决方案 –