Disconnected camera corrupts the related C# object

I’m developing a C# .NET 10 project using the Teledyne Dalsa Nano 5G M2450 camera.

The camera is directly connected to a 2.5GB NIC of the PC. It’s a 100Hz auto triggered transferring about 95MB/s of data image. The NIC settings are optimized by following your guide.

The frames capture uses the siNetFilter GigE Vision Driver.

C# dll I use are provided by your CVB v15.00.004 SDK (previously tested with last v14 but my issue is the same).

I followed your code examples to implement my app and it works as expected:

Device? device;
device = DeviceFactory.Open(DeviceFactory.Discover().FirstOrDefault());
device.ConnectionStateChanged += ConnectionStateChanged;
loop {
using var image = _device!.Stream.WaitFor(_streamWaitTimeout, out var status);
}

But sometimes the connection fails and come back but it corrupts the device object and I can’t recover as your examples suggest. Actions to abort/stop/restart the stream leads to a stemmer exception. I observe the same behavior when I disconnect the Ethernet cable and reconnect if.
The only workaround I found is to try to dispose the objet (sometimes it gets stuck indefinitely in this action) and instantiate a new one. But it’s painful because it take a while to recover and is not reliable.

There are some posts already discussing a similar problem but the fix doesn’t work for me or the discussion ended without a solution.

I’m bit confused on what I’m supposed to implement to handle connection state change and what I actually encounter.

Hello Michael,

could you show the exact code that you run when the ConnectionState changes back to Connected?

Cheers,

Tim

Here is the connection Event code:

private void ConnectionStateChanged(object? sender, ConnectionStateChangeEventArgs e)
{
	if (e.NewState == ConnectionState.Connected)
	{
		TriggerStartStream();
		Log.Debug($"{_sourceDevicePosition} camera event: reconnected");
	}
	else
	{
		TriggerStopStream();
		StatusMonitor.Update(this, _codes.Disconnected, _sourceDevicePosition, true);
		Log.Debug($"{_sourceDevicePosition} camera event: disconnected");
	}
}

When disconnected, TriggerStopStream() triggers a background task that processes this code:

private void DoStopStream()
{
	_grab = false;
	var device = _device;
	if (device?.Stream.IsRunning != true) { return; }
	Log.Debug($"{_sourceDevicePosition} camera: stopping acquisition");
	try
	{
		device.Stream.Stop();
	}
	catch (Exception ex)
	{
		Log.Debug($"Error stopping acquisition of {_sourceDevicePosition} camera: {ex.Message}");
	}
}

When re-connected, TriggerStartStream() triggers a background task that processes this code:

private bool DoStart(CancellationToken stoppingToken)
{
	if (_device is null) { return false; }
	if (_grab) { return true; }
	_stoppingToken = stoppingToken;
	_timeConverter = new(_device.NodeMaps[NodeMapNames.Device]);
	if (_acquisitionThread is null || !_acquisitionThread.IsAlive)
	{
		_grab = true;
		_acquisitionThread = new Thread(AcquireAndProcess)
		{
			Priority = ThreadPriority.Highest,
		};
		_acquisitionThread.Start();
		Log.Information($"{_sourceDevicePosition} camera: starting acquisition");
		return true;
	}
	return false;
}
private void AcquireAndProcess()
{
	if (_device.Stream.IsRunning) { return; }
	try
	{
		_device.Stream.Start();
		while (_grab && !_stoppingToken.IsCancellationRequested)
		{
			try
			{
				using var image = _device.Stream.WaitFor(_streamWaitTimeout, out var status);
				if (status == WaitStatus.Abort)
				{
					_grab = false;
					break;
				}
				else if (status == WaitStatus.Timeout)
				{
					Log.Debug($"Camera wait timeout of {_streamWaitTimeout.TotalMilliseconds}ms reached");
					continue
				}

				ProcessImage(image);
			}
			catch (InvalidOperationException)
			{
				_grab = false;
			}
			catch (Exception ex)
			{
				Log.Debug($"Unknown exception in camera");
				_grab = false;
			}
		}
	}
	catch (Exception ex)
	{
		Log.Error(ex, $"Failed to start camera stream");
		_grab = false;
	}
}

Hello Michael,

The issue is a race in the stream lifecycle: Stop(), Start(), WaitFor(), and IsRunning can run concurrently on the same stream. A reconnect can schedule Start() while the preceding disconnect task is still stopping, or while the acquisition thread is still inside WaitFor().

ConnectionStateChanged should only signal the state change. Have one owner for all CVB stream calls, preferably the acquisition worker, and serialize this sequence:

  1. On disconnect: cancel the token passed to WaitFor; do not call Stop() from a separate task.
  2. Let the acquisition worker leave WaitFor (WaitStatus.Abort), then stop/clean up the stream there.
  3. On reconnect: wait until that worker has fully completed its stop, then start it once with a fresh cancellation token.

On disconnect, call cvbCancellationSource.Cancel(). The worker should check cancellation even if an image arrived, then perform its own Stop()/TryStop() before reporting that it has stopped. Only after that completion signal may the reconnect path create a new worker and call Start().

Here’s a small example:

using Stemmer.Cvb;
using Stemmer.Cvb.Driver;
using System.Threading;

private Device _device = null!;
private Thread _acquisitionThread = null!;
private readonly object _stateLock = new();
private readonly AutoResetEvent _connectionChanged = new(false);

private bool _running = true;
private bool _connected;
private CancellationTokenSource? _waitCancellation;

public void StartCamera()
{
    _device = DeviceFactory.Open(DeviceFactory.Discover().First());
    _connected = _device.ConnectionState == ConnectionState.Connected;
    _device.ConnectionStateChanged += ConnectionStateChanged;

    _acquisitionThread = new Thread(AcquireImages);
    _acquisitionThread.Start();
}

private void ConnectionStateChanged(object? sender, ConnectionStateChangeEventArgs e)
{
    lock (_stateLock)
    {
        _connected = e.NewState == ConnectionState.Connected;

        // Only interrupt WaitFor. Do not call Stream.Stop or Stream.Start here.
        if (!_connected)
            _waitCancellation?.Cancel();
    }

    _connectionChanged.Set();
}

private void AcquireImages()
{
    var stream = _device.Stream;

    while (_running)
    {
        // Wait until the camera is connected.
        while (_running && !IsConnected())
            _connectionChanged.WaitOne(250);

        if (!_running)
            break;

        var cancellation = new CancellationTokenSource();

        lock (_stateLock)
        {
            if (!_connected)
                continue;

            _waitCancellation = cancellation;
        }

        try
        {
            stream.Start();

            while (_running && IsConnected())
            {
                using var image = stream.WaitFor(
                    TimeSpan.FromSeconds(1),
                    cancellation.Token,
                    out var status);

                if (status == WaitStatus.Abort)
                    break;

                if (status == WaitStatus.Timeout)
                    continue;

                ProcessImage(image);
            }
        }
        catch (Exception ex)
        {
            Log.Error(ex, "Camera acquisition failed");
        }
        finally
        {
            lock (_stateLock)
            {
                if (ReferenceEquals(_waitCancellation, cancellation))
                    _waitCancellation = null;
            }

            // This is the only place that stops the stream.
            stream.TryStop();
        }
    }
}

private bool IsConnected()
{
    lock (_stateLock)
        return _connected;
}

public void StopCamera()
{
    _running = false;

    lock (_stateLock)
        _waitCancellation?.Cancel();

    _connectionChanged.Set();
    _acquisitionThread.Join();
}

Cheers,

Tim