-
Notifications
You must be signed in to change notification settings - Fork 3
Description
First off, thanks for creating this library! It's a great abstraction for a very common problem.
I've been looking through the implementation and had some thoughts on the threading model that I wanted to share for discussion.
Current Design and Potential Issues
The library currently manages its own threading via the start_async method. While this is convenient, I've noticed a couple of potential issues with this approach:
-
Race condition. In threaded mode, the
_background_capturemethod writes toself._latest_frameat the same time the main application thread might be reading it viaget_latest_frame. (example) Since this access isn't synchronized with a lock, it could lead to race conditions like torn frames, especially under heavy load. -
Slightly confusing base class logic: In
VideoCaptureBase, the_read_frame_for_threadmethod contains a potentially confusing call toself.read(), which in turn calls other methods that check the thread's status. It seems to create a tangled execution path that could be hard to debug. The subclasses appear to handle this correctly by using a_read_direct method, but the base implementation itself is a bit confusing.
Proposal: A Synchronous Core with External Concurrency
The real strength of this library is how it handles each individual source type's settings, abstracts away their configurations using the factory method pattern, and implements the reading logic from each of the supported sources.
What if the library's core classes were simplified to be purely synchronous, leaving the concurrency management to the application developer?
My suggestion is to remove the internal threading logic (start_async, _background_capture, etc.) and provide a simple, blocking read() method as the primary way to get a frame.
Benefits of this Approach:
- The developer gains full control over how to run the reader: in a simple loop, wrap it in a
threading.Threadfor a producer-consumer pattern, or usemultiprocessing.Processto get around the GIL. The library no longer imposes a specific concurrency model. - This design eliminates the race condition by its very nature and simplifies the library's internal state, making it easier to maintain and reason about.
- The public API becomes more focused and predictable. A simple
connect(),disconnect(), andread()is very intuitive.
Suggested API and Usage:
The core VideoCapture classes would just have a clean, synchronous API:
class VideoCapture:
def connect(self): # Establish a connection
def disconnect(self): # Release the resource
def read(self): # Synchronously reads a single frame
def config(self): # Returns the source configuration The application developer would then be responsible for building their own producer logic, giving them full control over the architecture:
def frame_producer(source, frame_queue, stop_event):
try:
source.connect()
while not stop_event.is_set():
success, frame = source.read()
if not success:
break
frame_queue.put(frame)
finally:
source.disconnect()This frame_producer can then be run directly in the main application thread, or given to a threading.Thread or multiprocessing.Process depending on the application's specific needs.
This change would turn the FrameSource classes into predictable building blocks that can be used in a much wider variety of application architectures.
Would love to hear your thoughts on this idea!