CoreAnimation warning deleted thread with uncommitted CATransaction
Asked Answered
S

3

37

I am having issues with the following warning:

CoreAnimation: warning, deleted thread with uncommitted CATransaction; set CA_DEBUG_TRANSACTIONS=1 in environment to log backtraces.

I am using an NSOperation object to perform some calculations, once complete it sends a message back to the AppDelegate that then hides a progress bar and unhides some buttons. If I comment out the message back to the AppDelegate the warning goes away but the progress bar obviously remains visible and animated.

I am using xCode 4.4.1 and OSX 10.8.1, however, when I compile and run the code using the same version of xCode on OSX 10.7.4 I do not get the warning and the code runs as expected.

Setting the CA_DEBUG_TRANSACTIONS=1 environment variable shows the backtrace as coming from an NSControl setEnabled message in the AppDelegate.

The answer is probably staring me in the face but maybe I've had too much coffee!

Sclerodermatous answered 20/9, 2012 at 6:35 Comment(2)
I have been playing around with this a little today. I suspect the issue is that the NSOperation completes before CoreAnimation is finished with redrawing the UI elements. The backtrace showed the method being originally called from the NSOperation. I tried implementing an NSNotification from the NSOperation to tell the AppDelegate the calculation is complete, hoping this would mean the NSOperation can be released without affecting CoreAnimation, however the warning still occurs but this time the source is CoreFoundation?Sclerodermatous
In both cases the program appears to operate as expected and the warning only shows when run on OS 10.8.1 or 10.8.2Sclerodermatous
S
19

Your suspicions are right. If NSOperation completes before CoreAnimation is done performing, then you get a nice warning:

*CoreAnimation: warning, deleted thread with uncommitted CATransaction; set CA_DEBUG_TRANSACTIONS=1 in environment to log backtraces.*

This can also happen under some circumstances when a block that is dispatched on a queue triggers some work from CoreAnimation and returns before the CoreAnimation finishes.

The solution I use is simple: On a block or NSOperation that requests work from CoreAnimation, I check that the work has indeed been completed before exiting.

To give you a proof-of-concept example, this is a block to be dispatched on a dispatch queue. In order to avoid the warning, we check that the CoreAnimation is done before exiting.

^{

   // 1. Creating a completion indicator

   BOOL __block animationHasCompleted = NO;

   // 2. Requesting core animation do do some work. Using animator for instance.

   [NSAnimationContext runAnimationGroup:^(NSAnimationContext *context){
      [[object animator] perform-a-nice-animation];
   } completionHandler:^{
      animationHasCompleted = YES;
   }];

   // 3. Doing other stuff…

   …

   // 4. Waiting for core animation to complete before exiting

   while (animationHasCompleted == NO)
   {
       usleep(10000);
   }

}
Spicy answered 25/11, 2012 at 15:36 Comment(4)
This is not the best solution as it consumes unnecessary resources. See below for better solutions. Specifically, using performSelectorOnMainThread:withObject:waitUntilDone: or dispatch_async().Ind
This is a bad solution, all animations should be dispatched to the main thread.Elegance
@Elegance Actually animations are dispatched to the main thread. I don't see what's wrong with the solution.Spicy
@Spicy If the animation is dispatched to the main thread then there would be no need for the sleep loop(#4), which is unnecessary work. The reason for the warning is because the animation is started from another thread which is being deleted.Elegance
T
24

In keeping with standard Cocoa paradigms, the recommended solution here is to perform your Core Animation work on the main thread, easily done with GCD:

dispatch_async(dispatch_get_main_queue(), ^{
    [self.delegate redrawSomething];
});

In general it's poor form to call objects in contexts they don't expect, so a good rule of thumb is to always dispatch onto the main thread when delivering messages to external modules.

Some frameworks—like Core Location—with emit a log message if they are called from any context other than the main thread. Others will emit cryptic messages, such as your example here with Core Animation.

Tadpole answered 26/6, 2013 at 22:5 Comment(0)
S
19

Your suspicions are right. If NSOperation completes before CoreAnimation is done performing, then you get a nice warning:

*CoreAnimation: warning, deleted thread with uncommitted CATransaction; set CA_DEBUG_TRANSACTIONS=1 in environment to log backtraces.*

This can also happen under some circumstances when a block that is dispatched on a queue triggers some work from CoreAnimation and returns before the CoreAnimation finishes.

The solution I use is simple: On a block or NSOperation that requests work from CoreAnimation, I check that the work has indeed been completed before exiting.

To give you a proof-of-concept example, this is a block to be dispatched on a dispatch queue. In order to avoid the warning, we check that the CoreAnimation is done before exiting.

^{

   // 1. Creating a completion indicator

   BOOL __block animationHasCompleted = NO;

   // 2. Requesting core animation do do some work. Using animator for instance.

   [NSAnimationContext runAnimationGroup:^(NSAnimationContext *context){
      [[object animator] perform-a-nice-animation];
   } completionHandler:^{
      animationHasCompleted = YES;
   }];

   // 3. Doing other stuff…

   …

   // 4. Waiting for core animation to complete before exiting

   while (animationHasCompleted == NO)
   {
       usleep(10000);
   }

}
Spicy answered 25/11, 2012 at 15:36 Comment(4)
This is not the best solution as it consumes unnecessary resources. See below for better solutions. Specifically, using performSelectorOnMainThread:withObject:waitUntilDone: or dispatch_async().Ind
This is a bad solution, all animations should be dispatched to the main thread.Elegance
@Elegance Actually animations are dispatched to the main thread. I don't see what's wrong with the solution.Spicy
@Spicy If the animation is dispatched to the main thread then there would be no need for the sleep loop(#4), which is unnecessary work. The reason for the warning is because the animation is started from another thread which is being deleted.Elegance
I
9

Another way of ensuring any UI drawing occurs on the main thread, as described by Numist, is using the method performSelectorOnMainThread:withObject:waitUntilDone: or alternatively performSelectorOnMainThread:withObject:waitUntilDone:modes:

- (void) someMethod
{
    [...]

    // Perform all drawing/UI updates on the main thread.
    [self performSelectorOnMainThread:@selector(myCustomDrawing:)
                           withObject:myCustomData
                        waitUntilDone:YES];

    [...]
}

- (void) myCustomDrawing:(id)myCustomData
{
    // Perform any drawing/UI updates here.
}


For a related post on the difference between dispatch_async() and performSelectorOnMainThread:withObjects:waitUntilDone: see Whats the difference between performSelectorOnMainThread and dispatch_async on main queue?

Ind answered 5/11, 2013 at 1:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.