I'm trying to figure out how to update an indeterminate NSProgressIndicator in the UI using a secondary thread while the main thread does some heavy lifting, just like dozens of apps do.This snippet is based on Apple's "Trivial Threads" example using Distributed Objects (DO's):
// In the main (client) thread...
- (void)doSomethingSlow:(id)sender
{
[transferServer showProgress:self];
int ctr;
for (ctr=0; ctr <= 100; ctr++)
{
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
NSLog(#"running long task...");
}
}
// In the secondary (server) thread...
- (oneway void)showProgress:(Controller*)controller
{
[controller resetProgressBar];
float ticks;
for (ticks=0; ticks <= 100; ticks++)
{
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
[controller updateProgress:ticks];
NSLog(#"updating progress in UI...");
}
}
Unfortunately however, there's no way I can get both threads to run concurrently. Either the secondary thread will run and the main thread waits until it's finished OR the main thread runs followed by the secondary thread -- but not both at the same time.
Even if I pass a pointer to the server thread and ask it to update the progress bar directly (without calling the main thread back) it makes no difference. It seems that once the main thread enters a loop like this it ignores all objects sent to it. I'm still a novice with Obj-C and I'd really appreciate any help with this.
AppKit isn't thread-safe, at all. You have to update the UI from the main thread, or all sorts of crazy stuff will happen (or it just won't work).
The best way is to do your work on the secondary thread, calling back to the main thread when you need to update the UI:
-(void)doSomethingSlow:(id)sender {
[NSThread detachNewThreadSelector:#selector(threadedMethod) toTarget:self withObject:nil];
// This will return immediately.
}
-(void)threadedMethod {
int ctr;
for (ctr=0; ctr <= 100; ctr++) {
NSLog(#"running long task...");
[self performSelectorOnMainThread:#selector(updateUI)];
}
}
-(void)updateUI {
// This will be called on the main thread, and update the controls properly.
[controller resetProgressBar];
}
You might want to try switching your threads. In general, UI updates and user input are handled on the main thread, and heavy tasks are left for secondary threads.
Related
I've offloaded a long-running, synchronous, operation to a background thread. It takes a while to get going, but eventually it starts producing items very nicely.
The question is then how to consume then - while maintaining a responsive UI (i.e. responding to Paint and UserInput messages).
One lock-free example sets up a while loop; we consume items while they are items to consume:
// You call this function when the consumer receives the
// signal raised by WakeConsumer().
void ConsumeWork()
{
Thing item;
while ((item = InterlockedGetItemOffTheSharedList(sharedList)) != nil)
{
ConsumeTheThing(item);
}
}
The problem is that the background thread, once it gets going, can produce the items very quickly. This means that my while loop will never have a chance to stop. That means it will never go back to the message queue to respond to pending paint and mouse input events.
I've turned my asynchronous multi-threaded application in a synchronous wait as it sits inside:
while (StuffToDo)
{
Consume(item);
}
Posting Messages
Another idea is to have the background thread PostMessage a message to the main thread every time an item is available:
ProduceItemsThreadMethod()
{
Preamble();
while (StuffToProduce())
{
Thing item = new Item();
SetupTheItem(item);
InterlockedAddItemToTheSharedList(item);
PostMessage(hwndMainThreadListener, WM_ItemReady, 0, 0);
}
}
The problem with this is that any posted message is always higher priority than any:
paint messages
mouse move messages
So as long as there is posted messages available, my application will not be responding to paint and input messages.
while GetMessage(out msg)
{
DispatchMessage(out msg);
}
Every call to GetMessage will return a fresh WM_ItemReady message. My Windows message processing will be flooded with ItemReady messages - preventing me from processing paints until all the items have been added.
I've turned my asynchronous multi-threaded application in a synchronous wait.
Limiting the number of posted messages doesn't help
The above is actually worse than the first variation, because we flood the main thread with posted messages. What we want to do is only post a message if the main thread hasn't dealt with the previous message we posted. We can create a flag that is used to indicate if we've already posted a message, and if the main thread still hasn't processed it
ProduceItemsThreadMethod()
{
Preamble();
while (StuffToProduce())
{
Thing item = new Item();
SetupTheItem(item);
InterlockedAddItemToTheSharedList(item);
//Only post a message if the main thread has a message waiting
int oldFlagValue = Interlocked.Exchange(g_ItemsReady, 1);
if (oldFlagValue == 0)
PostMessage(hwndMainThreadListener, WM_ItemReady, 0, 0);
}
}
And in the main thread we clear the "ItemsReady" flag when we've processed the queued items:
void ConsumeWork()
{
Thing item;
while ((item = InterlockedGetItemOffTheSharedList(sharedList)) != nil)
{
ConsumeTheThing(item);
}
Interlocked.Exchange(g_ItemsReady, 0); //tell the thread it can post messages to us again
}
The problem again is that the thread can fill the list faster than we can consume it; so we never get a change to fall out of the ConsumeWork() function in order to handle user input.
As soon as ConsumeWork returns, the background producer thread generates a new WM_ItemReady message. The very next time i call GetMessage
while GetMessage(out msg)
{
DispatchMessage(out msg);
}
it will be a WM_itemReady message. I will be stuck in a loop.
I've turned my asynchronous multi-threaded application in a synchronous wait.
Limiting ourselves to a count of items doesn't help
We could try forcing a break out of the while loop after, say, processing 100 items:
void ConsumeWork()
{
int itemsProcessed = 0;
Thing item;
while ((item = InterlockedGetItemOffTheSharedList(sharedList)) != nil)
{
ConsumeTheThing(item);
itemsProcessed += 1;
if (itemsProcessed >= 250)
break;
}
Interlocked.Exchange(g_ItemsReady, 0); //tell the thread it can post messages to us again
}
This suffers from the same problem as the previous incarnation. Although we will leave the while loop, the very next message we will recieve will again be the WM_ItemReady:
while (GetMessage(...) != 0)
{
TranslateMessge(...);
DispatchMessage(...);
}
that's because WM_PAINT messages will only appear if there are no other messages. And the thread is itching to create a new WM_ItemReady message and post it in my queue.
Pumping the message loop myself?
Some people cry a little inside when they see people manually pumping messages to fix unresponsive applications. So lets try manually pumping messages to fix unresponsive applications!
void ConsumeWork()
{
Thing item;
while ((item = InterlockedGetItemOffTheSharedList(sharedList)) != nil)
{
ConsumeTheThing(item);
ManuallyPumpPaintAndInputEvents();
}
Interlocked.Exchange(g_ItemsReady, 0); //tell the thread it can post messages to us again
}
I won't go into the details of that function, because it leads to the re-entrancy problem. If the user of my library happens to try to close the window they're on, destroying my helper class with it, i will suddenly come back to execution inside a class that has been destroyed:
ConsumeTheThing(item);
ManuallyPumpPaintAndInputEvents(); //processes WM_LBUTTONDOWN messages will closes the window which destroys me
InterlockedGetItemOffTheSharedList(sharedList) //sharedList no longer exist BOOM
Down and down I go
I keep going in circles trying to solve the problem of how to maintain a responsive UI when using background threads. I've tinkered with four solutions in this question, and three others before asking it.
I can't be the first person to have used the Producer-Consumer model in a user interface.
How do you maintain a responsive UI?
If only i could post a message with priority lower than Paint, Input, and Timer :(
I'm debugging Qt5.3.1 on Mac, because my program freezes sometimes (intermittent ). I discovered that it is because the QTimer can't work properly.
In Qt code, they use the following two lines to trigger function activateTimersSourceCallback
CFRunLoopSourceSignal(d->activateTimersSourceRef);
CFRunLoopWakeUp(mainRunLoop());
void QCocoaEventDispatcherPrivate::activateTimersSourceCallback(void *info)
{
static int counter = 0;
NSLog(#"finished activeteTimersSourceCallback %d", counter++);
}
but sometimes, these two lines doesn't work, activateTimersSourceCallback won't get called.
I googled, but I couldn't find any solution? is this a known OS bug?
the initialization details:
// keep our sources running when modal loops are running
CFRunLoopAddCommonMode(mainRunLoop(), (CFStringRef) NSModalPanelRunLoopMode);
CFRunLoopSourceContext context;
bzero(&context, sizeof(CFRunLoopSourceContext));
context.info = d;
context.equal = runLoopSourceEqualCallback;
// source used to activate timers
context.perform = QCocoaEventDispatcherPrivate::activateTimersSourceCallback;
d->activateTimersSourceRef = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
Q_ASSERT(d->activateTimersSourceRef);
CFRunLoopAddSource(mainRunLoop(), d->activateTimersSourceRef, kCFRunLoopCommonModes);
Such behavior very likely can occur when UI event loop is overloaded with events or some business logic takes too long time. You should to check your business logic and move it to separate thread or run asynchronous.
I have an application written in xcode/cocoa on Mac.
A label on the main window is changed in every occurrence of a heavy loop with [label setStringValue], however it is refreshed only at the end of the loop.
How can I have it refreshed in each occurrence ?
Thanks !
You should use a queue. Your heavy loop in backgroundQueue and [label setStringValue] in mainQueue.
Example:
dispatch_queue_t backgroundQueue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(backgroundQueue,^{
//Your loop
dispatch_async(mainQueue,^{
//Set Label value
});
});
Your problem is that you do the work (the loop) on the main thread. The main thread is responsible for updating the UI and must not be blocked!
You need to start a new thread to do the heavy work and update your UI on the main thread.
You should have a look at GCD which is a good an lightweight solution for that or have a look at the performSelector... methods.
I'm writing an application that runs an algorithm, but allows you to 'step through' the algorithm by pressing a button - displaying what's happening at each step.
How do I listen for events while within a method?
eg, look at the code I've got.
static int proceed;
button1Event(GtkWidget *widget)
{
proceed = 0;
int i = 0;
for (i=0; i<15; i++) //this is our example 'algorithm'
{
while (proceed ==0) continue;
printf("the nunmber is %d\n", i);
proceed = 0;
}
}
button2Event(GtkWidget *widget)
{
proceed = 1;
}
This doesn't work because it's required to exit out of the button1 method before it can listen for button2 (or any other events).
I'm thinking something like in that while loop.
while(proceed == 0)
{
listen_for_button_click();
}
What method is that?
The "real" answer here (the one any experienced GTK+ programmer will give you) isn't one you will like perhaps: don't do this, your code is structured the wrong way.
The options include:
recommended: restructure the app to be event-driven instead; probably you need to keep track of your state (either a state machine or just a boolean flag) and ignore whichever button is not currently applicable.
you can run a recursive main loop, as in the other answer with gtk_main_iteration(); however this is quite dangerous because any UI event can happen in that loop, such as windows closing or other totally unrelated stuff. Not workable in most real apps of any size.
move the blocking logic to another thread and communicate via a GAsyncQueue or something along those lines (caution, this is hard-ish to get right and likely to be overkill).
I think you are going wrong here:
while(proceed == 0)
{
listen_for_button_click();
}
You don't want while loops like this; you just want the GTK+ main loop doing your blocking. When you get the button click, in the callback for it, then write whatever the code after this while loop would have been.
You could check for pending events & handle the events in while loop in the clicked callback. Something on these lines:
button1Event(GtkWidget *widget)
{
proceed = 0;
int i = 0;
for (i=0; i<15; i++) //this is our example 'algorithm'
{
while (proceed ==0)
{
/* Check for all pending events */
while(gtk_events_pending())
{
gtk_main_iteration(); /* Handle the events */
}
continue;
}
printf("the nunmber is %d\n", i);
proceed = 0;
}
}
This way when the events related click on the second button is added to the event queue to be handled, the check will see the events as pending and handle them & then proceed. This way your global value changes can be reflected & stepping should be possible.
Hope this helps!
If you want to do it like this, the only way that comes to my mind is to create a separate thread for your algorithm and use some synchronization methods to notify that thread from within button click handlers.
GTK+ (glib, to be more specific) has its own API for threads and synchronization. As far as I know Condition variables are a standard way to implement wait-notify logic.
I have a determinate progress indicator. It is working just like I would expect it to but it does not disappear after it reaches maxValue. I have set the progress indicator to not display when stopped in the main.nib file, I have also entered it into the awakeFromNib{} method.
I put a log at the end of the routine to make sure the [displayWhenStopped] setting was still set to NO and it is.
Here is my code :
-(void)getEvents:(NSURL *)mffFile{
NSMutableArray * eventTypeResults =[[NSMutableArray alloc]init];
EWaveformRecording *ptrToRcdFile = [EWaveformRecording openRecording:mffFile permission:readOnly user:nil convertFileFormat:NO openReadOnlyAnyway:NO];
NSArray *events = [ptrToRcdFile getEvents];
//get the size of events for the progressbar and instantiate a loop counter
NSInteger total = [events count];
int loop = 0;
//set progress bar params
[self->meter_ setUsesThreadedAnimation:YES];
[self->meter_ setControlSize:NSMiniControlSize];
[self->meter_ setMaxValue:(double)total];
for(EEvent* event in events){
loop ++;
if(![eventTypeResults containsObject:event.code]){
NSLog(#"check eventNames in getEvents %#", event.code);
[eventTypeResults addObject: event.code];
}//end if
//display loop increment in progress bar
[self->meter_ setDoubleValue:(1000*(double)loop)/1000];
}//end for
//send the eventTypesResults array to the EventExport class
[evtPtr setEventsAvailableList:eventTypeResults];
}
What I have tried:
with and without [setUsesThreadedAnimation] which I don't totally understand; it does slow down the progress bar which makes it look better but the docs say only indeterminate types should be effected by animation.
I have tried using [start & stop animation]
I have tried [setDisplayWhenStopped:NO] after my loop
Any help is greatly appreciated
MIke
This is what I learned.
I should not be allowing the progress bar to run on a different thread even though it looks like its working because the NSProgressIndicator can no longer respond to the settings in the main thread, so the proper thing to do is to not instantiate that method, however , that was not the solution to my problem; I was doing everything else right, but the main thread could not redraw the progress because it's busy with all the other calls in the UI. The solution was to implement NSRunLoop so that each iteration of the loop interrupts the main thread and redraws the progress meter , then returns control.