PDFDocument dataRepresentation is slow... How to show progress? - performance

I'm currently working on a Cocoa application that works with PDFs, and am using Apple's PDFKit to do the work. Saving the PDF is proving a problem, as I'd like to show a progress bar while that's happening, which doesn't seem to be possible using the standard writeToURL: method. So, I went and used Grand Central Dispatch instead.
The problem with this is that the dataRepresentation method used to get the NSData to write is terribly slow in converting any PDF larger than ~3MB to NSData, and so the progress bar stalls for a few seconds while my program is waiting for the data, which seems to make the users think the program has stalled completely. And I don't really want them thinking that.
My question is, is there anything I can do to either speed up the dataRepresentation method, or report its progress to the user?
Here's the Grand Central Dispatch code I ended up writing:
NSData *pdf = [doc dataRepresentation]; // R-e-a-l-l-y S-l-o-w
dispatch_queue_t queue = dispatch_get_current_queue();
dispatch_data_t dat = dispatch_data_create([pdf bytes], [pdf length], queue, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
__block dispatch_io_t channel =
dispatch_io_create_with_path(DISPATCH_IO_STREAM,
[path UTF8String], // Convert to C-string
O_WRONLY, // Open for writing
0, // No extra flags
queue,
^(int error){
// Cleanup code for normal channel operation.
// Assumes that dispatch_io_close was called elsewhere.
if (error == 0) {
dispatch_release(channel);
channel = NULL;
}
});
// The next two lines make sure that the block in dispatch_io_write
// gets called about 60 times, so that it can update the progress bar.
dispatch_io_set_low_water(channel,[pdf length]/60);
dispatch_io_set_high_water(channel,[pdf length]/60);
dispatch_io_write(channel,
0,
dat,
queue,
^(bool done, dispatch_data_t data, int error) {
if (data) {
// Increment progress bar
}
if (error) {
// Handle errors
}
if (done) {
dispatch_io_close(channel, NULL);
}
});
dispatch_release(dat);
dispatch_release(queue);

Use the PDFDocumentDidBeginPageWriteNotification and PDFDocumentDidEndPageWriteNotification notifications, which are sent during -writeToURL:. The notifications tell you what page is processing, which you can compare to the total number of pages in the document to show progress.

Related

Video Tool Box. pixel transfer, when to release source buffer?

One simple straight question, when to release source pixelbuffer after transferred image to avoid crash:
//pixel_buffer is the original
CVPixelBufferCreate(kCFAllocatorDefault,
CVPixelBufferGetWidth(pixel_buffer),
CVPixelBufferGetHeight(pixel_buffer),
kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,
NULL, &targetPxb);
if (targetPxb != NULL) {
auto status = VTPixelTransferSessionTransferImage(transSession,
pixel_buffer,
targetPxb);
if (status == noErr) {
// CFRelease(pixel_buffer); //this will cause crash
}
}
Check the reference count of pixel_buffer. If you did not add a CFRetain, executing a CFRelease will cause your application to crash since the reference count is already 0, so there is no need to call CFRelease in this case. There are simple ways of checking the reference count, for example:
CFGetRetainCount

How to keep UI responsive when consuming items produced by background thread producer?

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 :(

Using NSURLSession from a Swift command line program

I'm trying to test a little proof-of-concept command line app prior to integrating it into a larger app. What I'm trying to do is download some data using NSURLSession using this example. However it appears that if I use the examples given in a simple OS X command line app then the app exits prior to the data being retrieved.
How can I download data from a stand-alone command line app using NSURLSession? What I've read about is using NSRunLoop however I've not yet found a clear example in Swift so if NSRunLoop is actually the way to go then any examples would be appreciated.
Any other strategies for downloading data from a URL for a Swift command line app is also welcome (infinite while loop?).
You can use a semaphore to block the current thread and wait for your URL session to finish.
Create the semaphore, kick off your URL session, then wait on the semaphore. From your URL session completion callback, signal the semaphore.
You could use a global flag (declare a volatile boolean variable) and poll that from a while loop, but that is less optimal. For one thing, you're burning CPU cycles unnecessarily.
Here's a quick example I did using a playground:
import Foundation
var sema = DispatchSemaphore( value: 0 )
class Delegate : NSObject, URLSessionDataDelegate
{
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data)
{
print("got data \(String(data: data, encoding: .utf8 ) ?? "<empty>")");
sema.signal()
}
}
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config, delegate: Delegate(), delegateQueue: nil )
guard let url = URL( string:"http://apple.com" ) else { fatalError("Could not create URL object") }
session.dataTask( with: url ).resume()
sema.wait()
Try this
let sema = DispatchSemaphore( value: 0)
let url = URL(string: "https://upload.wikimedia.org/wikipedia/commons/4/4d/Cat_November_2010-1a.jpg")!;
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
print("after image is downloaded");
sema.signal(); // signals the process to continue
};
task.resume();
sema.wait(); // sets the process to wait
For proof of concept(s) or tryouts/testing purposes, you can simplify asynchronous complexity by hard coding some timeout period until your stuff finishes. (see notes below)
SWIFT 5
//...your magic here
// add a little 🤓iness to make it fun at least...
RunLoop.main.run(until: Date() + 0x10) //oh boi, default init && hex craze 🤗
// yeah, 16 seconds timeout
// or even worse (!)
RunLoop.main.run(until: .distantFuture)
SWIFT 3 or earlier
//...your stuff here
RunLoop.main.run(until: Date(timeIntervalSinceNow: 15)) //will execute things on main loop for 15 seconds
NOTES :
DO NOT USE THIS IN PRODUCTION
respect the first rule
This is very quick and dirty way to overcome serious concerns of parallelism. Explore better and more complex solutions described in other answers of this question.

DrawRect and NSProgressIndicator

I will be very short:
I'am drawing lines using drawrect and NSBezierPath.
To draw these lines I am using a for loop.
Now, since the loop takes many seconds,I'm trying to use a NSProgresIndicator to show the progress and to update it within the loop I used dispatch_async & queue.. The ProgressIndicator updates but nothing is being drawn.. If don't use the queue the lines are drawn but the indicator updates at the end of the cycle.
What am I doing wrong ?
The error:
<Error>: CGContextRestoreGState: invalid context 0x0.
This is a serious error.
This application, or a library it uses, is using an invalid context
and is thereby
contributing to an overall degradation of system stability and reliability.
This notice is a courtesy: please fix this problem.
It will become a fatal error in an upcoming update.
// SAMPLE OF CODE
dispatch_queue_t myQueue = dispatch_queue_create("my queue", NULL);
dispatch_async(myQueue, ^
{
for (int i = 1; i <= 200; i++) {
dispatch_async(dispatch_get_main_queue(), ^{
[istance set_progress:i];
//This sets the progress, but bezierPath doesn't work
});
//Bezier already alloc & init
[my_path moveToPoint....]
[my_path lineToPoint....]
[my_path stroke]
}
});
In general you shouldn't be drawing on a background thread or queue.
Please read this document for information about multithreaded drawing. Skip to "Drawing Restrictions"
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html

NSProgressIndicator will not disappear (determinate)

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.

Resources