将 NSOperation 子类化为并发且可取消
我无法找到有关如何将 NSOperation 子类化以实现并发并支持取消的良好文档。我阅读了苹果文档,但找不到“官方”示例。
这是我的源代码:
@synthesize isExecuting = _isExecuting;
@synthesize isFinished = _isFinished;
@synthesize isCancelled = _isCancelled;
- (BOOL)isConcurrent
{
return YES;
}
- (void)start
{
/* WHY SHOULD I PUT THIS ?
if (![NSThread isMainThread])
{
[self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO];
return;
}
*/
[self willChangeValueForKey:@"isExecuting"];
_isExecuting = YES;
[self didChangeValueForKey:@"isExecuting"];
if (_isCancelled == YES)
{
NSLog(@"** OPERATION CANCELED **");
}
else
{
NSLog(@"Operation started.");
sleep(1);
[self finish];
}
}
- (void)finish
{
NSLog(@"operationfinished.");
[self willChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
_isExecuting = NO;
_isFinished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
if (_isCancelled == YES)
{
NSLog(@"** OPERATION CANCELED **");
}
}
在我发现的示例中,我不明白为什么使用performSelectorOnMainThread:。它会阻止我的操作同时运行。
另外,当我注释掉该行时,我的操作会同时运行。但是,即使我已调用 cancelAllOperations
,isCancelled
标志也不会被修改。
I am unable to find good documentation about how to subclass NSOperation
to be concurrent and also to support cancellation. I read the Apple docs, but I am unable to find an "official" example.
Here is my source code :
@synthesize isExecuting = _isExecuting;
@synthesize isFinished = _isFinished;
@synthesize isCancelled = _isCancelled;
- (BOOL)isConcurrent
{
return YES;
}
- (void)start
{
/* WHY SHOULD I PUT THIS ?
if (![NSThread isMainThread])
{
[self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO];
return;
}
*/
[self willChangeValueForKey:@"isExecuting"];
_isExecuting = YES;
[self didChangeValueForKey:@"isExecuting"];
if (_isCancelled == YES)
{
NSLog(@"** OPERATION CANCELED **");
}
else
{
NSLog(@"Operation started.");
sleep(1);
[self finish];
}
}
- (void)finish
{
NSLog(@"operationfinished.");
[self willChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
_isExecuting = NO;
_isFinished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
if (_isCancelled == YES)
{
NSLog(@"** OPERATION CANCELED **");
}
}
In the example I found, I don't understand why performSelectorOnMainThread: is used. It would prevent my operation from running concurrently.
Also, when I comment out that line, I get my operations running concurrently. However, the isCancelled
flag is not modified, even though I have called cancelAllOperations
.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
好的,据我了解,您有两个问题:
您是否需要代码注释中出现的
performSelectorOnMainThread:
段?该代码的作用是什么?当您在包含此操作的
NSOperationQueue
上调用cancelAllOperations
时,为什么_isCancelled
标志未修改?让我们按顺序处理这些。为了便于解释,我假设您的
NSOperation
子类名为MyOperation
。我会解释你的误解,然后给出一个正确的例子。1. 同时运行 NSOperations
大多数时候,您会将
NSOperation
与NSOperationQueue
结合使用,从您的代码来看,这听起来像是您正在做的事情。在这种情况下,您的MyOperation
将始终在后台线程上运行,无论-(BOOL)isConcurrent
方法返回什么,因为NSOperationQueue
被明确设计为在后台运行操作。因此,您通常不需要重写
-[NSOperation start]
方法,因为默认情况下它只是调用-main
方法。这是您应该重写的方法。默认的-start
方法已在适当的时间为您处理设置isExecuting
和isFinished
。因此,如果您希望 NSOperation 在后台运行,只需覆盖
-main
方法并将其放入NSOperationQueue
中即可。代码中的
performSelectorOnMainThread:
会导致MyOperation
的每个实例始终在主线程上执行其任务。由于一个线程上一次只能运行一段代码,这意味着不能运行其他MyOperation
。NSOperation
和NSOperationQueue
的全部目的是在后台执行某些操作。唯一一次你想要将事情强制到主线程上是当你更新用户界面时。如果您需要在
MyOperation
完成时更新 UI,那么您应该使用performSelectorOnMainThread:
。我将在下面的示例中展示如何做到这一点。2. 取消 NSOperation
-[NSOperationQueue cancelAllOperations]
会调用-[NSOperation cancel]
方法,这会导致后续调用-[NSOperation isCancelled]
返回YES
。 但是,您做了两件事使其无效。您正在使用
@synthesize isCancelled
来覆盖NSOperation的-isCancelled
方法。没有理由这样做。NSOperation
已经以完全可接受的方式实现了-isCancelled
。您正在检查自己的
_isCancelled
实例变量以确定操作是否已被取消。NSOperation
保证如果操作已被取消,[self isCancelled]
将返回YES
。它不保证您的自定义 setter 方法将被调用,也不保证您自己的实例变量是最新的。您应该检查[self isCancelled]
您应该做什么
标题:
和实现:
请注意,您不需要对
isExecuting
、isCancelled 执行任何操作
或isFinished
。这些都是自动为您处理的。只需重写-main
方法即可。就是这么简单。(注意:从技术上讲,这不是“并发”
NSOperation
,因为-[MyOperation isConcurrent]
在实现时会返回NO
但是,它将在后台线程上运行。isConcurrent
方法确实应该命名为-willCreateOwnThread
,因为这是一个更准确的名称。该方法意图的描述。)Okay, so as I understand it, you have two questions:
Do you need the
performSelectorOnMainThread:
segment that appears in comments in your code? What does that code do?Why is the
_isCancelled
flag is not modified when you callcancelAllOperations
on theNSOperationQueue
that contains this operation?Let's deal with these in order. I'm going to assume that your subclass of
NSOperation
is calledMyOperation
, just for ease of explanation. I'll explain what you're misunderstanding and then give a corrected example.1. Running NSOperations concurrently
Most of the time, you'll use
NSOperation
s with anNSOperationQueue
, and from your code, it sounds like that's what you're doing. In that case, yourMyOperation
will always be run on a background thread, regardless of what the-(BOOL)isConcurrent
method returns, sinceNSOperationQueue
s are explicitly designed to run operations in the background.As such, you generally do not need to override the
-[NSOperation start]
method, since by default it simply invokes the-main
method. That is the method you should be overriding. The default-start
method already handles settingisExecuting
andisFinished
for you at the appropriate times.So if you want an
NSOperation
to run in the background, simply override the-main
method and put it on anNSOperationQueue
.The
performSelectorOnMainThread:
in your code would cause every instance ofMyOperation
to always perform its task on the main thread. Since only one piece of code can be running on a thread at a time, this means that no otherMyOperation
s could be running. The whole purpose ofNSOperation
andNSOperationQueue
is to do something in the background.The only time you want to force things onto the main thread is when you're updating the user interface. If you need to update the UI when your
MyOperation
finishes, that is when you should useperformSelectorOnMainThread:
. I'll show how to do that in my example below.2. Cancelling an NSOperation
-[NSOperationQueue cancelAllOperations]
calls the-[NSOperation cancel]
method, which causes subsequent calls to-[NSOperation isCancelled]
to returnYES
. However, you have done two things to make this ineffective.You are using
@synthesize isCancelled
to override NSOperation's-isCancelled
method. There is no reason to do this.NSOperation
already implements-isCancelled
in a perfectly acceptable manner.You are checking your own
_isCancelled
instance variable to determine whether the operation has been cancelled.NSOperation
guarantees that[self isCancelled]
will returnYES
if the operation has been cancelled. It does not guarantee that your custom setter method will be called, nor that your own instance variable is up to date. You should be checking[self isCancelled]
What you should be doing
The header:
And the implementation:
Note that you do not need to do anything with
isExecuting
,isCancelled
, orisFinished
. Those are all handled automatically for you. Simply override the-main
method. It's that easy.(A note: technically, this is not a "concurrent"
NSOperation
, in the sense that-[MyOperation isConcurrent]
would returnNO
as implemented above. However, it will be run on a background thread. TheisConcurrent
method really should be named-willCreateOwnThread
, as that is a more accurate description of the method's intention.)@BJHomer 的出色答案值得更新。
并发操作应该重写
start
方法而不是main
。正如 Apple 文档中所述:
如果您正在创建并发操作,您至少需要重写以下方法和属性:
start
asynchronous
executing
finished
A正确的实现还需要覆盖
cancel
。使子类线程安全并获得正确的所需语义也相当棘手。因此,我将一个完整且工作的子类作为 在代码审查中用 Swift 实现的提案。欢迎提出意见和建议。
该类可以轻松用作自定义操作类的基类。
The excellent answer of @BJHomer deserves an update.
Concurrent operations should override the
start
method instead ofmain
.As stated in the Apple Documentation:
If you are creating a concurrent operation, you need to override the following methods and properties at a minimum:
start
asynchronous
executing
finished
A proper implementation also requires to override
cancel
as well. Making a subclass thread-safe and getting the required semantics right is also quite tricky.Thus, I've put a complete and working subclass as a proposal implemented in Swift in Code Review. Comments and suggestion are welcome.
This class can be easily used as a base class for your custom operation class.
我知道这是一个老问题,但我最近一直在调查这个问题,遇到了同样的例子,也有同样的疑问。
如果您的所有工作都可以在 main 方法内同步运行,则不需要并发操作,也不需要覆盖 start,只需完成您的工作并在完成后从 main 返回。
但是,如果您的工作负载本质上是异步的 - 即加载 NSURLConnection,则必须子类化 start。当您的 start 方法返回时,操作尚未完成。仅当您手动向 isFinished 和 isExecuting 标志发送 KVO 通知时(例如,一旦异步 URL 加载完成或失败),NSOperationQueue 才会认为已完成。
最后,当您要启动的异步工作负载需要在主线程上进行运行循环侦听时,您可能希望将启动分派到主线程。由于工作本身是异步的,因此它不会限制并发性,但在工作线程中启动工作可能没有准备好适当的运行循环。
I know this is an old question, but I've been investigating this lately and encountered the same examples and had the same doubts.
If all your work can be run synchronously inside the main method, you don't need a concurrent operation , neither overriding start, just do your work and return from main when done.
However, if your workload is asynchronous by nature - i.e loading a NSURLConnection, you must subclass start. When your start method returns, the operation isn't finished yet. It will only be considered finished by the NSOperationQueue when you manually send KVO notifications to the isFinished and isExecuting flags (for instance, once the async URL loading finishes or fails).
Lastly, one might want to dispatch start to the main thread when the async workload you want to start require a run-loop listening on the main thread. As the work itself is asynchronous, it won't limit your concurrency, but starting the work in a worker thread might not have a proper runloop ready.
看一下 ASIHTTPRequest。它是一个 HTTP 包装类,作为子类构建在 NSOperation 之上,并且似乎实现了这些。请注意,截至 2011 年中期,开发人员建议不要在新项目中使用 ASI。
Take a look at ASIHTTPRequest. It is an HTTP wrapper class built on top of
NSOperation
as a subclass and seems to implement these. Note that as of mid-2011, the developer recommends not using ASI for new projects.关于在 NSOperation 子类中定义“cancelled”属性(或定义“_cancelled”iVAR),通常这是不必要的。仅仅是因为当 USER 触发取消时,自定义代码应始终通知 KVO 观察者您的操作现在已完成。换句话说,isCancelled =>
特别是,当 NSOperation 对象依赖于其他操作对象的完成时,它会监视这些对象的 isFinished 键路径。因此,未能生成完成通知(发生取消情况)可能会阻止应用程序中其他操作的执行。
顺便说一句,@BJ Homer 的回答:“isConcurrent 方法确实应该命名为 -willCreateOwnThread”,很有道理!
因为如果你不重写start-method,只需手动调用NSOperation-Object的default-start-method,调用线程本身默认是同步的;所以,NSOperation-Object只是一个非并发操作。
但是,如果您确实重写了启动方法,在启动方法实现中,自定义代码应该生成一个单独的线程...等等,那么您就成功地打破了“调用线程默认是同步的”的限制,因此,使 NSOperation-Object 成为并发操作,之后可以异步运行。
Regarding define "cancelled" property (or define "_cancelled" iVAR) inside NSOperation subclass, normally that is NOT necessary. Simply because when USER triggers the cancellation, custom code should always notify KVO observers that your operation is now finished with its work. In other words, isCancelled => isFinished.
Particularly, when NSOperation object is dependent on the completion of other operation objects, it monitors the isFinished key path for those objects. Failing to generate a finish notification (in case of cancellation happens) can therefore prevent the execution of other operations in your application.
BTW, @BJ Homer's answer: "The isConcurrent method really should be named -willCreateOwnThread" makes A LOT sense!
Because if you do NOT override start-method,simply manually call NSOperation-Object's default-start-method, calling-thread itself is, by default, synchronous; so, NSOperation-Object is only a non-concurrent operation.
However, if you DO override start-method, inside start-method implementation, custom-code should spawn a separate thread…etc,then you successfully break the restriction of "calling-thread default being synchronous", therefore making NSOperation-Object becoming a concurrent-operation, it can run asynchronously afterwards.
此博客文章:
http://www.dribin.org/ dave/blog/archives/2009/09/13/snowy_concurrent_operations/
解释了为什么您可能需要:
在您的
start
方法中。This blog post:
http://www.dribin.org/dave/blog/archives/2009/09/13/snowy_concurrent_operations/
explains why you might need:
in your
start
method.