Re: Getting mouse clicks when the main loop is busy
Re: Getting mouse clicks when the main loop is busy
- Subject: Re: Getting mouse clicks when the main loop is busy
- From: Graham Cox <email@hidden>
- Date: Fri, 08 Nov 2013 20:17:29 +0100
Hi Fritz, Kyle.
First off, Kyle is quite right, it’s the -nextEventMatchingMask: I needed. In fact that’s where I started, but didn’t quite get all the pieces lined up. I was only fetching mouse down events, and passing them directly to the button for tracking. That worked, except that the highlighting wasn’t visible, because nothing was given a chance to update the graphics while the button did its tracking. So instead, asking for all events and letting -sendEvent: process them, I had good responsiveness and graphics updates.
There were two problems with that approach. First, it was too broad, in that clicks in other windows were being processed which I don’t want - I only need the progress window’s clicks. But If you use the NSWindow variants of the NSApp methods for fetching and dispatching events, first you find the documentation says that you should “never invoke” -[NSWindow sendEvent:] even though it worked more or less fine, but I also found that clicks in other windows were not quite being rejected as I expected, and events seem to queue up gradually killing performance as my file is opened.
So, finally I realised that what I really needed was a modal session for the progress, which dequeues events destined for other windows and beeps on clicks that do not target the modal window. Using runModalSession: I get everything I wanted.
The more philosophical question is why do this on the main thread?
Part of it has to do with keeping my options open - I can use a thread or not. The situation is in fact handling the file read for NSDocument, so setting +canConcurrentlyReadDocumentsOfType: selects whether a thread is used or not. However, I’ve discovered that if you do allow it to use a thread, there are two unpleasant usability side effects.
If the file is small and can be read quickly, it really doesn’t make much difference either way whether it’s threaded or not. But if it’s a large file, there may be some time (even minutes) between clicking ‘Open’ in the NSOpenPanel and the document opening its window. During that time, there is no feedback whatsoever that anything is happening. It acts, for all intents and purposes, as if the ‘Open’ click was ignored. Because the app remains responsive, you might try again, and now you have the same file being opened on two threads (unless something takes care to prevent this with NSDocument, I haven’t investigated). The user simply thinks your app is broken. But then, bang, the document windows open unexpectedly at some indeterminate future time. It was trying to mitigate this terrible lack of feedback that motivated having a progress indicator for file opening. But in fact, simply not opening them concurrently and just blocking the main thread while they open is much better from the user’s point of view - they can tell that something is happening, even if they have to wait until it’s done. The app may be unresponsive, but at least it doesn’t seem broken. And having the app responsive while a file is opening is of minimal benefit it seems to me - the user wanted to work on another file, that’s why they opened it - so the time waiting for this file to open on a thread is not time that is likely to be fruitfully spent editing some other file.
The other reason, which is more surprising, is that my assumption was that the Versions browser would work better if the documents were opened on a background thread. The reality is, it works far worse. It seems to ask the app to start decoding as many files as it can when you enter Versions, and pretty soon my machine is blowing its fans and is so stuttery it’s impossible to use. When the document is opening its files on the main thread, this doesn’t happen - it seems Versions is happy enough to let it open one file at a time and only goes back to the app to decode a file as it is displayed. This appears to be a much lower workload, as my laptop stays cool and Versions remains nice and responsive. (Incidentally it would have been a lot nicer if Versions could have worked by asking your app to return some sort of image representing the file or window content rather than getting it to decode the actual file. The Versions browser is totally at the mercy of your app, and sometimes it’s simply not possible to decode a file in a short time. Grabbing a screenshot of the window which Versions could cache would be far more deterministic - it would only then need to ask your app to open an actual file when Versions was closed with a different document choice).
All in all, we found that turning OFF concurrent document opening was a better user experience overall.
And that is why the progress dialog needs to integrate with the main thread.
—Graham
On 8 Nov 2013, at 7:04 pm, Fritz Anderson <email@hidden> wrote:
> On 8 Nov 2013, at 11:16 AM, Graham Cox <email@hidden> wrote:
>
>> What’s the proper way to do this? I *can* open the file on another thread and let the main thread run normally, and that’s fine, but I do need a solution that works for when the file is opened on the main thread.
>
> With the humblest intention in the world, and admitting I don't know your use case…
>
> You seem to suggest that "opening the file on the main thread" might be unavoidable. Is this because the process of identifying, opening, reading, processing, and closing the file might be confined to a third-party library that isn't thread-safe? I can't imagine another way you couldn't pass a path, URL, or file descriptor into a thread. (I have a limited imagination.)
>
> If you're not in that situation, I suggest doing whatever you prudently can to load the file on a background thread every time. It factors your app by cutting your code paths in half, and you don't have to rely on cooperative multitasking to keep up a responsive UI. If you're just filling a buffer or interpreting a stream from a file, and don't need to display partial results other than progress, you're in about the least-hard thread-confinement situation you can have.
>
> That's my esthetic. There are excellent developers who use a cooperative runloop with good results.
>
> — F
>
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden