A program that runs several while loops against one USB camera fails in a specific way: you press stop or abort, the process dies mid-frame, and the camera will not open again until you unplug it. The loops were the problem, not the camera. This guide gives the stop design that prevents it: one shared stop request every loop reads, a bounded wait on every blocking call, one acknowledgement per loop, and a fixed release order — plus the timing budget that tells you how long a clean stop takes at your frame rate. The arithmetic works for two loops or ten, and for USB3 and GigE interfaces alike.
Why an abort leaves the camera dead
Killing the process from outside skips every cleanup your machine-vision SDK would normally run. The operating system keeps the camera's USB interface claimed, the frame in flight never finishes cleanly, and the device stays reserved for a session that no longer exists. Restarting the program does not help, because the claim belongs to the dead process, and the camera stays busy or invisible until you unplug it.
The fix is order, not force: stop acquisition, flush buffers, close the handle, then exit. If a crash has already happened, recovery is its own subject — see camera disappears after a capture crash: diagnose USB recovery. Everything below is about never needing it.
The design: one stop signal, every loop acknowledges
The standard architecture here is the producer–consumer pattern: one loop produces commands or work, the others consume them. Your stop control becomes one more command, produced once and consumed by every loop in the program.
Three ways to carry the signal, best first:
- Broadcast flag or notifier. One shared stop request that every loop reads each iteration and no loop consumes. Reads return the current value, so any number of loops can read the same stop without disturbing each other.
- Queue messages. Workable, but you must enqueue one stop message per consumer loop, ahead of any pending work, or a loop keeps processing a full queue before it sees the stop.
- Per-loop events. One event per loop, all fired together. Same effect, more wiring.
For camera programs the broadcast wins, because a stop is state, not data: every loop must see it, and reading it must not use it up. A walkthrough that stops several program loops with a single shared control is on the control-software knowledge base.
What you need
- The SDK trio that ends a session: the stop-acquisition, buffer-flush and close calls every machine-vision SDK exposes.
- One shared stop request, or one stop message per consumer loop.
- One acknowledgement flag per loop.
- A bounded timeout on every blocking call: frame wait, queue pop, sleep.
- A stop test that runs at full frame rate, mid-grab.
The stop sequence, step by step
- Map loops to resources. Decide which loop owns the camera handle. One owner stops and closes it; every other loop only stops itself.
- Create the stop request. One shared flag, initially false, set true once by the UI loop. It stays true until the process exits.
- Bound every wait. Give frame waits and queue pops a timeout around 100 ms, so each loop rechecks the stop request about ten times per second. This is what makes an outer stop effective: a loop parked in an unbounded wait never polls anything, which is why stops aimed "around" loops appear to do nothing.
- Acquisition loop. On stop: call stop acquisition, drain or discard buffered frames, close the camera, then set its acknowledgement.
- Processing loop. On stop: finish or drop the queued frames by design, release its buffers, exit, acknowledge.
- UI loop. Set the stop request once, then wait for all acknowledgements under an overall timeout.
- Main thread. Exit after every acknowledgement, or after the timeout plus a log line naming the loop that missed its acknowledgement.
- Prove it. Stop mid-grab at full frame rate, then run 20 start/stop cycles. Passing means the camera enumerates every cycle and the next session opens without a replug.
In skeleton form, with illustrative variable names — substitute your SDK's real stop, flush and close calls:
stop_requested = False # bool, set once by the UI loop
ack = {} # dict, one bool per worker loop
# acquisition loop
while not stop_requested:
frame = wait_for_frame(timeout_ms=100) # bounded wait
if frame is not None:
processing_queue.put(frame)
stop_acquisition()
flush_buffers()
close_camera()
ack["acquisition"] = True
How long a clean stop takes
A clean stop waits for at most one more frame, then pays the SDK teardown. Compute the budget from the frame rate:
The 100 ms margin is a bench default for the stop-acquisition and close calls; if your SDK logs teardown times, substitute its measured value. Frame rate moves the budget linearly, and the two cameras below put numbers on it.
For scale: a 12 MP mono frame is roughly 12 MB, and USB3's practical ceiling near 400 MB/s is what holds 12 MP sensors around 30 fps.
- Sensor format
- 1/1.7"
- Interface
- USB3
- Resolution
- 12 MP
- Shutter
- Rolling
Worked example at 30 fps: T = 1/30 s ≈ 33.3 ms, so the stop budget is 2 × 33.3 ms + 100 ms ≈ 167 ms. A stop pressed mid-frame lands within one frame period, and the whole program exits in under a fifth of a second.
At a gentler pace the same 12 MP sensor over GigE buys margin: the LS-CMR-0053 reaches 9 fps. At 9 fps, T = 1/9 s ≈ 111 ms and the budget grows to 2 × 111 ms + 100 ms ≈ 322 ms — still far below the one-second join timeouts most shutdown code uses.
- Sensor format
- 1/1.7"
- Lens mount
- C-mount
- Interface
- GigE
- Resolution
- 12 MP
The number of loops does not stretch the budget: acknowledgements arrive in parallel, so the join waits for the slowest loop, not the sum of all loops.
Common mistakes
- A stop only one loop sees. A one-shot flag consumed by its first reader leaves the rest running. A status-style broadcast fixes it, because nothing consumes the value.
- Unbounded waits. A frame wait or queue pop with no timeout hides the stop flag for seconds at a stretch. One bounded wait per iteration keeps the exit check alive ten times a second; the one wait call per frame rule is the acquisition-side half of the same habit.
- Two owners closing one camera. A double release breaks a session as surely as no release. One owner closes; everyone else exits.
- Treating a shutdown timeout as a fault. After the stop request is set, a frame-wait timeout is the expected exit path — branch on the stop flag before you log an error.
- Stopping around the loops instead of inside them. An outer loop's exit condition has no authority over inner loops that never read it. Deliver the stop to each loop.
USB3 versus GigE worst cases
The release order is identical; the punishment for skipping it is not. A killed process on USB3 can leave the device unenumerable until a replug, and reopening a handle that was closed without stopping acquisition ends in timeouts — the stop acquisition before you close failure. GigE cameras usually recover from a hard process death after a host-side restart of the camera, the recovery path the crash-diagnosis article linked above walks through. Both interfaces follow the same stop, flush, close sequence in a clean shutdown, so one design covers both. The examples use 12 MP area scan cameras, but nothing in the handshake depends on resolution.
Checks before you ship
- Stop pressed mid-grab at full frame rate: the program exits inside the computed budget and the camera is still enumerated.
- Twenty consecutive start/stop cycles: no replug, and the first open of each new session raises no timeout.
- Stop with the camera unplugged before start: the program still exits, because every wait is bounded.
- Acknowledgement log with timestamps per loop: shows which loop consumed most of the budget and whether the margin holds.
- One deliberate kill from the operating system, once, on the bench: confirms your recovery path, then stays out of production.
When the simple answer is right
One acquisition loop with no second consumer and no separate UI loop needs no broadcast: stop acquisition, flush, close, exit — the same release order, one place. And when a start fails because a previous session never stopped cleanly, that is the single-frame acquisition still busy case: every start needs a matching stop, which is exactly the bookkeeping the handshake above automates.
Pick the camera and interface for a new station with the camera finder, or start at the camera step of the configurator. More acquisition-architecture fixes sit on the systems topic page, and you can contact the applications team for a review of your stop design before commissioning.
Questions
Why does the camera vanish from the device list until I replug it?
The dead process never released the USB interface, so the operating system keeps the device reserved for a session that no longer exists. A replug or driver reset clears the stale claim. The handshake in this guide prevents the state by closing the handle while the program still runs; the crash-diagnosis article covers recovery once it has already happened.
How long should the shutdown join wait for all loops to acknowledge?
Two frame periods plus the teardown margin: about 167 ms for a 30 fps camera and about 322 ms at 9 fps, both including the 100 ms margin. If an acknowledgement is still missing at the timeout, log which loop missed and exit anyway. A bounded exit beats a hung shutdown, and a stale claim is recoverable by replug in the worst case.
Does one stop flag per loop work better than a shared one?
No — a shared broadcast is simpler and cannot be consumed by whoever reads first. Keep the per-loop item on the other side: each loop sets its own acknowledgement flag so the main thread can join on all of them. Queue-based designs are the exception: they need one stop message per consumer, queued ahead of pending work.
Does the 100 ms bounded wait cost frame throughput?
No. The timeout bounds only how long a loop waits when no frame arrives. While frames flow at 30 fps — one every 33 ms — the timeout never fires. Its entire job is the stopped state: it lets a loop with no incoming frames notice the stop request within 100 ms instead of hanging forever.
Is the shutdown design different for GigE Vision and USB3 Vision cameras?
The handshake is identical; the worst case after a hard kill differs. A USB3 device can stay unenumerable until replug, while a GigE camera usually comes back after a network power-cycle or host restart. During a clean shutdown both interfaces run the same stop-acquisition, flush, close sequence, so one stop design covers either interface.

