System designExplainer

Camera data queue get vs tryGet: block or poll

get blocks, tryGet returns None: the four data queue calls, the backlog math that picks the right one, and loop patterns for display, inference and PLC cycles.

LumaSpec Vision11 min read Share on LinkedIn

On this page 11 sections
  1. 01The four calls in one table
  2. 02The backlog math that decides
  3. 03Worked example: a 30 fps camera under two host loops
  4. 04When to block: get and getAll
  5. 05When to poll: tryGet and has
  6. 06When the All variants earn their keep
  7. 07Design for the hang
  8. 08Where the loop runs
  9. 09The decision, case by case
  10. 10Checks before you commit
  11. 11Questions

Four calls read a camera's data output queue — get, getAll, tryGet, tryGetAll — and picking the wrong one either ages your detections by seconds or hangs the application at shutdown. The choice comes down to one question: does your loop set its own clock, or does it wait for the camera? This page gives the producer-consumer arithmetic behind that question, a worked example at 30 fps, and the call to use for display, guidance, recording and PLC-side loops. Everything here runs on the host, so it applies to any streaming camera.

The four calls in one table

Most camera SDKs deliver each stream — frames, depth maps, detection lists — as a FIFO queue object, and some name the class DataOutputQueue outright. Two independent switches separate the read calls: does the call wait, and does it hand back one message or every queued message.

Call Waits? Returns Built for
get Yes, until a message arrives One message, oldest first Loops that must process every message
getAll Yes, like get All queued messages Complete result sets from multi-shot triggers
tryGet No One message or None Poll loops that act on the latest state
tryGetAll No All available or None Drain-and-keep-newest; never-drop accumulation
has No True or False Test first; keep the previous result untouched

has() is the quiet fifth tool: it reports whether anything is queued without reading. has() plus get and tryGet plus a None check are functionally the same — the real differences show up in the loop you wrap around them.

The backlog math that decides

Write C for the camera rate in frames/s and L for the host loop's read rate in reads/s. Three regimes decide everything. If L ≥ C, the queue stays empty and a non-blocking read adds at most one poll interval of latency. If L < C, the queue grows at C − L frames/s no matter which call you use — blocking only serializes the delay, it never creates capacity. And because messages leave a FIFO queue oldest first, a slow loop reading with get ages its data exactly like a slow loop polling with tryGet and keeping every message.

C = camera rate (frames/s) L = loop read rate (reads/s) backlog growth = C − L frames/s (when C > L) message age at read ≈ backlog / C s poll latency at L ≥ C: 0 … (1/C + 1/L) s

The line-level version of the same bottleneck is worked through in acquisition faster than processing: fix the frame rate gap.

Worked example: a 30 fps camera under two host loops

Camera: C = 30 frames/s. Loop A is a display overlay running at L = 60 reads/s with tryGet. Since L ≥ C the queue never accumulates. Worst-case age of the drawn frame: 1/30 + 1/60 = 0.033 + 0.017 = 0.050 s = 50 ms. The average added queue wait is 1/(2 × 60) = 0.008 s ≈ 8 ms. Decision: tryGet is correct here; nothing to fix.

Loop B runs an inference that takes 100 ms per iteration, so L = 10 reads/s. Backlog growth: 30 − 10 = 20 frames/s. After 3 s the queue holds 20 × 3 = 60 messages, and every read hands over a frame aged 60 / 30 = 2.0 s. Buffer memory grows too: a 5 MP 8-bit mono frame is 5,000,000 bytes ≈ 5 MB, so 60 frames ≈ 300 MB before SDK overhead. Switching Loop B to a blocking get changes nothing: it still consumes one message per 100 ms, so the backlog grows identically — just synchronously.

Loop B must skip on purpose. Drain with tryGetAll at the top of each iteration and keep the newest message: each poll returns on average C / L = 30 / 10 = 3 messages, and the backlog stays flat because you empty it every time. The newest state is then between 1/30 ≈ 33 ms and 1/10 + 1/30 = 0.100 + 0.033 = 0.133 s ≈ 133 ms old, about 67 ms on average. If instead every detection must be counted, fold all ~3 messages per poll into an accumulator and discard the frames — nothing is dropped. And if the inspection is gated, one frame per part, the only correct fix is rate: get at 30 fps needs the loop to finish inside 33 ms, or the camera slowed to 10 fps.

Drain-and-keep-newest, in full (queue and variable names are illustrative; tryGetAll and .detections are the documented names):

latest = None                    # newest message seen so far
while running:                   # host loop, ~10 Hz
    msgs = q_det.tryGetAll()     # all available messages or None
    if msgs:                     # covers None and empty
        latest = msgs[-1]        # FIFO: last element is the newest
    if latest is not None:
        for d in latest.detections:
            ...                  # act on the current state

Run it for ten seconds at C = 30 fps: each poll returns 2–4 messages, the loop keeps its fixed cadence, and the age of latest never exceeds about 133 ms. That measurement is the test that proves the pattern.

When to block: get and getAll

Blocking reads belong where every message matters: recording, archiving, gated inspection with one frame per part, and a capture thread that feeds several consumers. get gives the loop its rhythm from the camera side — each call returns exactly one fresh message in capture order, and there is no None branch to write. getAll extends that to multi-shot triggers: one call returns the complete set a trigger queued, instead of N timed reads. The pattern for building an image set from a single trigger is in multi image capture per trigger.

The precondition is rate. A get-based loop consumes one message per iteration, so the iteration must finish inside 1/C — 33 ms at 30 fps. If it cannot, the FIFO turns determinism into delay: you still read every message, but each one arrives stale. On a gated line the fix is never poll-and-discard, because a discarded frame is a part that passed uninspected — slow the camera or speed the loop. Keep parameter writes out of this hot read loop; the safe way to set and get camera parameters in a capture loop shows the ordering that avoids that stall.

When to poll: tryGet and has

Polling belongs where the loop acts on the latest state and answers to its own clock: display overlays, HMIs, robot guidance toward the part's current pose, and supervisory loops that also talk to PLCs. tryGet returns immediately; on iterations where nothing new arrived, you keep the previous result and re-use it. That is where has() reads better than a None check: the guard leaves the last good detection list untouched, and the code states its intent. Initialize that stored result to an empty list at boot — an undefined first value is the classic reference error on the first frames.

Multi-camera rigs are the other natural polling fit: one loop checks several queues at their own rates instead of blocking on each in turn. The switch from long waits to polling in a multi-camera rig works through that case.

When the All variants earn their keep

Three jobs. First, complete sets: getAll in the capture thread returns everything a multi-shot trigger queued, in one call. Second, never-drop accumulation: when each message carries detections you must count but the consuming loop runs slow, tryGetAll hands over everything queued since the last poll — about 3 messages at C = 30 frames/s and L = 10 reads/s — and you fold them in before acting. Third, drain-and-keep-newest: same call, keep only the last message. Draining also caps buffer memory when a latest-only mode keeps serving the same stale frames; why a latest-image setting returns duplicates and how draining clears them covers that failure.

Design for the hang

A blocking read carries no timeout in the call itself: get waits until a new message arrives. If the trigger never fires or the link drops, the thread parks forever, and a pending blocking read can stall the queue's close when the application exits. Three patterns keep the application alive: run blocking reads on a dedicated worker thread the shutdown path can abandon; poll with tryGet plus a short sleep so every iteration sees a stop flag; or put a watchdog on loop cycle time so a frozen read alarms instead of stalling silently. The timeout side of waiting for frames is worked out in frame didn't arrive within timeout: one wait call per frame.

The factory side is a consumer too. A result reaching the reject mechanism after the part has already passed is the same backlog math wearing a PLC tag; a resolved case — camera queue drift against an inspect-and-reject cycle — is written up in this queue-drift fix for an inspect-and-reject line.

Where the loop runs

All four calls execute on the host, so the controller sets the ceiling on L. Give each camera its own LAN port and, where you block, its own thread; the vision controllers category filters by port count, PoE and built-in light channels, so you can narrow the host by the same fields you sized the loop on.

The MV-VC3301H-128G66 vision controller has 5+ LAN ports, 4 built-in light channels and 8 GB of memory, driven by an Intel Core i3.

Vision controllersHikrobot MV-VC3301H-128G66 I3-8100, 8GB+128G SSD, No PoE, PCIe expansion supported, Incl. VM6200Pro licenseMV-VC3301H-128G66
LAN ports
5+ LAN ports
USB 3 ports
3-4 USB 3 ports
Light outputs
4 built-in channels
Memory
8 GB
$2,419.00View partBuild a system with it

For more cameras, the MV-VC5525-256G70-NN has PoE camera ports, 8 built-in light channels and 32 GB of memory on an Intel Core i5.

Vision controllersHikrobot MV-VC5525-256G70-NN VC5000 Vision Controller, 12th-I5, 32GB+256G SSD, Supported PCIEMV-VC5525-256G70-NN
LAN ports
5+ LAN ports
USB 3 ports
3-4 USB 3 ports
PoE
PoE camera ports
Light outputs
8 built-in channels
$4,479.00 · quotedView and quoteBuild a system with it

Where the camera decides on its own, a smart camera shrinks the host's job to reading finished results. On the depth side, point clouds from 3D sensors weigh far more per message than mono frames, so a backlog there costs memory faster; the system configurator pairs sensor, light and controller before you write the loop.

The decision, case by case

  • Recording or archiving every frame: get on a dedicated capture thread, iteration under 1/C.
  • Gated reject, one frame per part: get, and never skip — a skipped frame is an uninspected part.
  • Robot guidance to the latest pose: tryGetAll, keep the newest; budget its age at up to 1/C + 1/L.
  • HMI overlay: tryGet or has + get, keep the last result between frames.
  • Counting detections from a slow supervisor: tryGetAll, fold every message, discard the frames.
  • A script that must exit on demand: poll with tryGet and a sleep; a bare get parks the shutdown.

Every row is the same equation in different clothes: match L to C, or drain and skip deliberately.

Checks before you commit

  1. Measure C and L for ten seconds from timestamps; compute C − L before choosing the call.
  2. Confirm every tryGet handles None and no get carries a dead None branch.
  3. Initialize stored results — empty detection lists — at boot, before the first frame.
  4. Kill the trigger mid-run and exit: the application must shut down with a read pending.
  5. Budget latency end to end: capture timestamp to actuation, against the 1/C + 1/L figure.
  6. Run a 60 s stall test with the camera stopped and watch buffer memory stay at your drain bound.

More loop-level patterns like these — buffering, threading, one wait per frame — are collected on the systems topic page.

Questions

Does tryGet return None when the queue is empty?

Yes, and some SDK builds return an empty list instead. Treat both as nothing new: a truthiness check such as `if msgs:` covers None and empty in one line. The mirror image is why a None test after `get()` is dead code — `get` does not return until a message exists — and why `has()` is the cleaner guard when the loop must keep its previous result untouched.

Can a blocking get() wait forever?

Yes: it blocks until a new message arrives, with no timeout of its own. Order the shutdown so it cannot bite — stop the stream or the worker first, join the read thread, then close the queues; closing a queue behind a pending blocking read is what stalls exit. If you cannot reorder, poll with `tryGet` plus a short sleep so every iteration can see a stop flag.

Which call keeps every detection when my loop is slower than the camera?

tryGetAll: each poll returns everything queued since the last one — about 3 messages at 30 fps with a 10 Hz loop — so fold every message's detections into your accumulator and discard the frames. The same drain caps memory: at 5 MB per 8-bit 5 MP mono frame the queue now peaks near 15 MB instead of growing at 20 frames/s. For newest-state use, keep only the last message.

Does getAll block like get?

Yes: the blocking split between getAll and tryGetAll matches get versus tryGet. getAll waits only for availability — one queued message is enough — then returns everything queued, so in a fast loop it returns almost immediately. Put getAll in a capture thread that owns the camera; use tryGetAll in a shared loop that also drives a display or a PLC handshake, where waiting is not allowed.

How do I measure which pattern my loop needs?

Log two timestamps for ten seconds: the newest message timestamp in the queue and your read time. A flat gap near zero means the poll loop is faster than the camera. A gap that climbs means consumption is slower: divide the climb by the frame period and you have the backlog growth C − L in frames/s, which tells you to block, drain and skip, or cut the camera rate.