macos 如何在长时间运行的循环中更新 Cocoa 中的进度条?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2509612/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do I update a progress bar in Cocoa during a long running loop?
提问by Nick
I've got a while
loop, that runs for many seconds and that's why I want to update a progress bar (NSProgressIndicator) during that process, but it updates only once after the loop has finished. The same happens if I want to update a label text, by the way.
我有一个while
循环,它运行了很多秒,这就是为什么我想在该过程中更新进度条 (NSProgressIndicator),但它仅在循环完成后更新一次。顺便说一下,如果我想更新标签文本,也会发生同样的情况。
I believe, my loop prevents other things of that application to happen. There must be another technique. Does this have to do with threads or something? Am I on the right track? Can someone please give me a simple example, how to “optimize” my application?
我相信,我的循环会阻止该应用程序的其他事情发生。必须有另一种技术。这与线程有关吗?我在正确的轨道上吗?有人能给我一个简单的例子,如何“优化”我的应用程序?
My application is a Cocoa Application (Xcode 3.2.1) with these two methods in my Example_AppDelegate.m
:
我的应用程序是一个 Cocoa 应用程序(Xcode 3.2.1),在我的以下两种方法中Example_AppDelegate.m
:
// This method runs when a start button is clicked. - (IBAction)startIt:(id)sender { [progressbar setDoubleValue:0.0]; [progressbar startAnimation:sender]; running = YES; // this is a instance variable int i = 0; while (running) { if (i++ >= processAmount) { // processAmount is something like 1000000 running = NO; continue; } // Update progress bar double progr = (double)i / (double)processAmount; NSLog(@"progr: %f", progr); // Logs values between 0.0 and 1.0 [progressbar setDoubleValue:progr]; [progressbar needsDisplay]; // Do I need this? // Do some more hard work here... } } // This method runs when a stop button is clicked, but as long // as -startIt is busy, a click on the stop button does nothing. - (IBAction)stopIt:(id)sender { NSLog(@"Stop it!"); running = NO; [progressbar stopAnimation:sender]; }
I'm really new to Objective-C, Cocoa and applications with a UI. Thank you very much for any helpful answer.
我对带有 UI 的 Objective-C、Cocoa 和应用程序真的很陌生。非常感谢您提供任何有用的答案。
回答by Enchilada
If you are building for Snow Leopard, the easiest solution is in my opinion to use blocks and Grand Central Dispatch.
如果您正在为 Snow Leopard 构建,我认为最简单的解决方案是使用块和 Grand Central Dispatch。
The following code shows you how your startIt:
method would look like when using GCD.
以下代码向您展示了startIt:
使用 GCD 时您的方法的外观。
Your stopIt:
method should work fine as you wrote it. The reason why it wasn't working before is that mouse events happen on the main thread and thus the button didn't respond to you because you were doing work on the main thread. This issue should have been resolved now as the work has been put on a different thread now with GCD. Try the code, and if it doesn't work, let me know and I will see if I made some errors in it.
您的stopIt:
方法在编写时应该可以正常工作。之前它不起作用的原因是鼠标事件发生在主线程上,因此按钮没有响应您,因为您正在主线程上工作。这个问题现在应该已经解决了,因为现在 GCD 已经把工作放到了不同的线程上。尝试代码,如果它不起作用,请告诉我,我会看看我是否在其中犯了一些错误。
// This method runs when a start button is clicked.
- (IBAction)startIt:(id)sender {
//Create the block that we wish to run on a different thread.
void (^progressBlock)(void);
progressBlock = ^{
[progressbar setDoubleValue:0.0];
[progressbar startAnimation:sender];
running = YES; // this is a instance variable
int i = 0;
while (running) {
if (i++ >= processAmount) { // processAmount is something like 1000000
running = NO;
continue;
}
// Update progress bar
double progr = (double)i / (double)processAmount;
NSLog(@"progr: %f", progr); // Logs values between 0.0 and 1.0
//NOTE: It is important to let all UI updates occur on the main thread,
//so we put the following UI updates on the main queue.
dispatch_async(dispatch_get_main_queue(), ^{
[progressbar setDoubleValue:progr];
[progressbar setNeedsDisplay:YES];
});
// Do some more hard work here...
}
}; //end of progressBlock
//Finally, run the block on a different thread.
dispatch_queue_t queue = dispatch_get_global_queue(0,0);
dispatch_async(queue,progressBlock);
}
回答by zonble
You can try?this code ..
你可以试试吗?这个代码..
[progressbar setUsesThreadedAnimation:YES];
回答by Peter Hosey
I believe, my loop prevents other things of that application to happen.
我相信,我的循环会阻止该应用程序的其他事情发生。
Correct. You need to break this up somehow.
正确的。你需要以某种方式打破它。
One way would be a timer, with you whittling away the queue a little at a time in the timer callback. Another would be to wrap the code to handle one item in an NSOperation subclass, and create instances of that class (operations) and put them into an NSOperationQueue.
一种方法是使用计时器,您可以在计时器回调中一次一点地减少队列。另一种方法是包装代码以处理 NSOperation 子类中的一个项目,并创建该类的实例(操作)并将它们放入 NSOperationQueue。
Does this have to do with threads or something?
这与线程有关吗?
Not necessarily. NSOperations run on threads, but the NSOperationQueue will handle spawning the thread for you. A timer is a single-threaded solution: Every timer runs on the thread you schedule it on. That can be an advantage or a disadvantage—you decide.
不必要。NSOperations 在线程上运行,但 NSOperationQueue 将为您处理生成线程。计时器是一种单线程解决方案:每个计时器都在您安排它的线程上运行。这可能是优势也可能是劣势——由您决定。
See the threads section of my intro to Cocoafor more details.
有关更多详细信息,请参阅我对 Cocoa 的介绍的线程部分。
回答by keystonesw
This worked for me, which is a combination of answers from others that did not seem to work (for me at least) on their own:
这对我有用,这是其他人的答案的组合,这些答案似乎对自己不起作用(至少对我而言):
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
//do something
dispatch_async(dispatch_get_main_queue(), ^{
progressBar.progress = (double)x / (double)[stockList count];
});
//do something else
});