Up to version .44 this code in an add-on gave you a spinning wheel while the action was running, after .45+ this rotating wheel is no longer shown so that there’s no feedback that the action is running.
How would you fix the code for .45+? Is there another add-on that actually shows the rotating wheel during longer running actions on versions .45+?
(the fact that direct calls worked in the past was due to ugly hacks that updated the UI when progress.update() or the DB progress hook was called - these resulted in choppy updates, and could lead to crashes)
@dae Could you add some documentation on how to update the progress bar in this case?
Also I think there is some errors in the documentation code. The function is name “my_background_op” in the def but “my_background_operation” elsewhere. Also the loop variable is called id, but then the loop uses “note_id”.
def **my_background_op**(col: Collection, note_ids: list[int]) -> int:
# some long-running op, eg
for **id** in note_ids:
note = col.get_note(**note_id**)
# ...
return 123
def on_success(count: int) -> None:
showInfo(f"my_background_op() returned {count}")
def my_ui_action(note_ids: list[int]):
op = QueryOp(
# the active window (main window in this case)
parent=mw,
# the operation is passed the collection for convenience; you can
# ignore it if you wish
op=lambda col: **my_background_operation**(col, note_ids),
# this function will be called if op completes successfully,
# and it is given the return value of the op
success=on_success,
)
# if with_progress() is not called, no progress window will be shown.
# note: QueryOp.with_progress() was broken until Anki 2.1.50
op.with_progress().run_in_background()
Thank you, I’ve fixed the incorrect function name. That page has an example of how progress can be updated from within the op - see the “run_on_main” part.
I’ve been checking the docs and the source and just want to understand in this thread – if I’m using QueryOp, the UI shouldn’t lock up with a spinner and the user should be able to do whatever at that time, right? And if I call .with_progress() the user should be able to see a progress bar of some sorts?
Right now, my UI locks up with a spinner. I’m running some image scrapers in the background, and I’m launching one query op per note. It looks something like this:
jobs = []
processed_notes = set()
updated_notes: List[Note] = []
def on_success(updated_result):
processed_notes.add(updated_result.note_id)
updated_notes.append(apply_result_to_note(updated_result))
for _, note_id in enumerate(note_ids, 1):
note = mw.col.get_note(note_id)
op = QueryOp(
parent=mw,
op=lambda _: BingScraper().launch_scrape_job(config),
success=on_success)
op.with_progress().run_in_background()
Per the docs, I’ve double checked that BingScraper() has nothing side-effecting the UI. It sends requests, does some image processing, etc but the module using that scraper is doing nothing related to Qt. If I understood correctly, I must be accidentally blocking the main thread somehow – any advice on what to look out for in this case, or am I using QueryOp incorrectly here?
apply_result_to_note doesn’t persist anything to the database – the notes are collected and sent to update_notes later (I’ve checked that that’s not the part it’s blocking on).
With the snippet below (in Anki’s REPL) you can see that the behaviour is exactly like you described.
I would incrementally add your logic to it until it stops working.
from aqt.operations import QueryOp
def sleep(_):
import time
time.sleep(3)
for i in range(10):
op = QueryOp(parent=mw, op=sleep, success=lambda _, i=i: print(i))
op.with_progress().run_in_background()
There is some overhead when running a background op, so you typically want to make an operation that does all the work in one go, instead of creating lots of separate operations for each id. If you want bounded parallelism (eg 4 network requests in flight), then you’ll need to roll your own solution.
Yeah, perhaps it’s an issue of volume, in this case I was kicking off about 1 op per card and testing it on anywhere from 30-50 cards. With the on_success callback it shouldn’t be too hard to implement a loop or some type of queue to throttle the rate of the ops.
While of course it’s wasteful to create more ops than is necessary for your use case, I don’t understand how this would block the main thread. And your 30-50 queries are committed to a threadpool, so it’s not as if they would actually all run in parallel.