-
Notifications
You must be signed in to change notification settings - Fork 462
Improve camera refresh logic during initialization #2909
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
* Add PlatformRefreshAvailableCameras * Don't invoke refresh camera task when one is in progress * Remove redundant refresh camera calls in CameraManager
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR optimizes camera initialization performance by eliminating redundant calls to RefreshAvailableCameras()
and implementing concurrent refresh protection. The optimization reduces multiple simultaneous camera refresh operations from up to four calls down to a single execution during initialization.
- Consolidates camera refresh logic to occur only once during initialization in
CameraManager.ConnectCamera()
- Implements task-based deduplication in
CameraProvider
to prevent concurrent refresh operations - Removes redundant refresh calls from
CameraViewHandler
and platform-specificCameraManager
implementations
Reviewed Changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 4 comments.
Show a summary per file
File | Description |
---|---|
CameraView.shared.cs | Simplifies error handling logic in GetAvailableCameras() method |
CameraProvider.*.cs | Changes RefreshAvailableCameras() from public to private platform implementation and adds shared task deduplication |
CameraViewHandler.shared.cs | Removes redundant camera refresh call from ConnectHandler() |
CameraManager.*.cs | Removes redundant refresh calls from platform implementations and consolidates initialization logic |
CameraViewPage.xaml* | Adds initialization guard to prevent unnecessary refresh operations in sample app |
if (refreshAvailableCamerasTask is null || refreshAvailableCamerasTask.IsCompleted) | ||
{ | ||
refreshAvailableCamerasTask = PlatformRefreshAvailableCameras(token).AsTask(); | ||
} | ||
|
||
await refreshAvailableCamerasTask; |
Copilot
AI
Oct 16, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current implementation has a race condition where multiple threads could simultaneously check refreshAvailableCamerasTask
and both create new tasks. Consider using a lock or Interlocked.CompareExchange
to ensure thread-safe task creation.
Copilot uses AI. Check for mistakes.
{ | ||
if (refreshAvailableCamerasTask is null || refreshAvailableCamerasTask.IsCompleted) | ||
{ | ||
refreshAvailableCamerasTask = PlatformRefreshAvailableCameras(token).AsTask(); |
Copilot
AI
Oct 16, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The CancellationToken
from the current call is used to create the task, but subsequent callers with different tokens will await a task that may not respect their cancellation requirements. Consider how to handle cancellation tokens properly across multiple concurrent calls.
Copilot uses AI. Check for mistakes.
{ | ||
await cameraProvider.RefreshAvailableCameras(token); | ||
} | ||
cameraView.SelectedCamera ??= cameraProvider.AvailableCameras?.FirstOrDefault() ?? throw new CameraException("No camera available on device"); |
Copilot
AI
Oct 16, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] This line combines null-conditional assignment, null-conditional access, null-coalescing, and exception throwing in a single statement, making it difficult to read and debug. Consider breaking this into multiple lines for better clarity.
cameraView.SelectedCamera ??= cameraProvider.AvailableCameras?.FirstOrDefault() ?? throw new CameraException("No camera available on device"); | |
var availableCameras = cameraProvider.AvailableCameras; | |
if (availableCameras is null || !availableCameras.Any()) | |
{ | |
throw new CameraException("No camera available on device"); | |
} | |
if (cameraView.SelectedCamera is null) | |
{ | |
cameraView.SelectedCamera = availableCameras.First(); | |
} |
Copilot uses AI. Check for mistakes.
if (filteredPropertiesList.Count is 0) | ||
{ | ||
filteredPropertiesList = [.. cameraView.SelectedCamera.ImageEncodingProperties.OrderByDescending(p => p.Width * p.Height)]; | ||
} | ||
|
||
if (filteredPropertiesList.Count is not 0) |
Copilot
AI
Oct 16, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The conditional pattern Count is 0
is inconsistent with the pattern used elsewhere in the codebase. Consider using Count == 0
or Count is not 0
consistently throughout the file for better readability.
if (filteredPropertiesList.Count is 0) | |
{ | |
filteredPropertiesList = [.. cameraView.SelectedCamera.ImageEncodingProperties.OrderByDescending(p => p.Width * p.Height)]; | |
} | |
if (filteredPropertiesList.Count is not 0) | |
if (filteredPropertiesList.Count == 0) | |
{ | |
filteredPropertiesList = [.. cameraView.SelectedCamera.ImageEncodingProperties.OrderByDescending(p => p.Width * p.Height)]; | |
} | |
if (filteredPropertiesList.Count != 0) |
Copilot uses AI. Check for mistakes.
As suggested in #2907, I've created this PR to improve the performance for refresh during camera initialization. Can you please take a look, thanks! |
Description of Change
Root cause
Multiple calls to
CameraProvider.RefreshAvailableCameras()
are scattered throughout the camera initialization code (i.e. inCameraManager
andCameraViewHandler
.), affecting performance. Ideally, the refresh should only occur once during initialization.In Addition, developers may also call
RefreshAvailableCameras()
in the application code (e.g. in a ViewModel for the CameraView) at the same time when the camera view is initializing. This results in duplicate concurrent executions.For example, when opening the CameraView page in the sample app, a breakpoint set in
CameraProvider.RefreshAvailableCameras()
is hit up to four times from the following call stacks:CameraViewViewModel.RefreshCameras()
CameraManager.PlatformConnectCamera()
CameraManager.PlatformStartCameraPreview()
CameraViewHandler.ConnectHandler
This PR includes the following changes
RefreshAvailableCameras()
calls inCameraManager
andCameraViewHandler
. Only call the function once inCameraManager.Shared.ConnectCamera()
to ensure camera list is populated before connecting the camera.PlatformRefreshAvailableCameras
andrefreshAvailableCamerasTask
in CameraProvider to prevent duplicate concurrent refreshes. If multiple refreshes are invoked around the same time (e.g., from the ViewModel and the internal camera initialization), subsequent calls simply await the existing task.CameraViewPage
. Removed unused code and addisInitialized
guard to prevent refresh when navigated back from the photo page to the camera view page.Linked Issues
PR Checklist
approved
(bug) orChampioned
(feature/proposal)main
at time of PRAdditional information
This PR has been tested on Windows. Number of invocation of the refresh logic is reduced to one, without altering existing behaviour.