Skip to content

Conversation

zhitaop
Copy link
Contributor

@zhitaop zhitaop commented Oct 16, 2025

Description of Change

Root cause

Multiple calls to CameraProvider.RefreshAvailableCameras() are scattered throughout the camera initialization code (i.e. in CameraManager and CameraViewHandler.), 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

  • Remove redundant RefreshAvailableCameras() calls in CameraManager and CameraViewHandler. Only call the function once in CameraManager.Shared.ConnectCamera() to ensure camera list is populated before connecting the camera.
  • Introduce private PlatformRefreshAvailableCameras and refreshAvailableCamerasTask 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.
  • Some small changes in the sample app CameraViewPage. Removed unused code and add isInitialized guard to prevent refresh when navigated back from the photo page to the camera view page.

Linked Issues

PR Checklist

  • Has a linked Issue, and the Issue has been approved(bug) or Championed (feature/proposal)
  • Has tests (if omitted, state reason in description)
  • Has samples (if omitted, state reason in description)
  • Rebased on top of main at time of PR
  • Changes adhere to coding standard

Additional information

This PR has been tested on Windows. Number of invocation of the refresh logic is reduced to one, without altering existing behaviour.

* Add PlatformRefreshAvailableCameras

* Don't invoke refresh camera task when one is in progress

* Remove redundant refresh camera calls in CameraManager
@Copilot Copilot AI review requested due to automatic review settings October 16, 2025 01:33
Copy link
Contributor

@Copilot Copilot AI left a 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-specific CameraManager 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

Comment on lines +36 to +41
if (refreshAvailableCamerasTask is null || refreshAvailableCamerasTask.IsCompleted)
{
refreshAvailableCamerasTask = PlatformRefreshAvailableCameras(token).AsTask();
}

await refreshAvailableCamerasTask;
Copy link

Copilot AI Oct 16, 2025

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();
Copy link

Copilot AI Oct 16, 2025

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");
Copy link

Copilot AI Oct 16, 2025

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.

Suggested change
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.

Comment on lines +176 to 181
if (filteredPropertiesList.Count is 0)
{
filteredPropertiesList = [.. cameraView.SelectedCamera.ImageEncodingProperties.OrderByDescending(p => p.Width * p.Height)];
}

if (filteredPropertiesList.Count is not 0)
Copy link

Copilot AI Oct 16, 2025

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.

Suggested change
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.

@zhitaop
Copy link
Contributor Author

zhitaop commented Oct 16, 2025

@TheCodeTraveler @bijington

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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] CameraView App crashes when switching camera on Windows

1 participant