RingBufferImage disposed automatically

I made a simple program that acquire data from a camera using the new .Net CVB library.
I configured properly the device and I set the RingBufferLock Mode to ON.
device.Stream.RingBuffer.LockMode = RingBufferLockMode.On;

If I understand correctly from the documentation every time I invoke StreamImage image = await m_stream.WaitAsync() it place the image acquired in the ring buffer and it locks the buffer for that specific image. It is up to me to unlock the image from the ringbuffer usingimage.Unlock();, in order to avoid to fill in completely the ringBuffer and don’t have enough space for new images.
This behavior has been described also by @parsd in
IRingBuffer for Task-based ProcessingDifference between Cvb.SharedImg and Cvb.Image.IMG

After you have your desired number of buffers, you can set the lock mode to RINGBUFFER_LOCKMODE_ON via the RBLockMode function. This is a manual mode in which the acquisition engine only locks the buffers to write-protect it for the engine. You as the user must unlock the buffer via RBUnlock when you don’t need it anymore. Otherwise no buffers are available anymore after the ring buffer is filled and no new images can be acquired.

The strange thing is that after retrieving the image from the ring buffer in this way:
RingBufferImage image = m_stream.RingBuffer[lastAcquiredImageIndex];
and the RingBufferImage goes out of scope, it unlock automatically the slot in the buffer.

Am I doing something wrong?

I copy part of my code, in this way it should be more clear.

private Stream m_stream;

public void InitCamera()
{
	var device = LoadCameraDriver();
	device.Stream.RingBuffer.LockMode = RingBufferLockMode.On;
	device.Stream.RingBuffer?.ChangeCount(NUM_DRIVER_BUFFERS, DeviceUpdateMode.NewDeviceImage);

	m_stream = device.Stream;
}

public async Task AcquireData(CancellationTokenSource cts)
{
	m_stream.Start();

	while (!cts.Token.IsCancellationRequested)
	{
		using (StreamImage image = await m_stream.WaitAsync())
		{
			Tuple<IntPtr, int> res = GetBaseAddressAndBuffIndex(cts);
			var baseAddress = res.Item1;
			var buffIndex = res.Item2;
			var processingTask = Task.Run(async () => { await ProcessImage(baseAddress, buffIndex, imgTimeStamp, cts); });
			AddTask(PulseCounter, processingTask);
		}
	}

	List<Task> f = m_TaskDictionary.Values.ToList();
	Task.WaitAll(f.ToArray());
}

 private Tuple<IntPtr, int> GetBaseAddressAndBuffIndex(CancellationTokenSource cts)
{
	// last buffer acquired by G2Wait
	var lenght = m_stream.RingBuffer.GetAcquisitionSequence().Length;
	var lastAcquiredImageIndex = m_stream.RingBuffer.GetAcquisitionSequence()[lenght - 1];
	RingBufferImage image = m_stream.RingBuffer[lastAcquiredImageIndex];

	if (!image.IsLocked)
	{
		cts.Cancel();
		throw new ScannerException("Ring buffer image has not been locked.");
	}
	LinearAccessData imageData = image.Planes[0].GetLinearAccess();

	return new Tuple<IntPtr,int>(imageData.BasePtr, image.BufferIndex);
}

private async Task ProcessImage(IntPtr baseAddress, int buffIndex, long imgTimeStamp, CancellationTokenSource cts)
{
	RingBufferImage image = m_stream.RingBuffer[buffIndex];

	if (image.IsLocked)
	{
		// Do some stuff
		imageProcessed
		
		// dispose object
		image.Unlock();
		await Writer.WriteImageToFileAsync(imageProcessed);
	}
	else
	{
	   Console.WriteLine("Scanner: {0} - Ring buffer image has not been locked.Buffer Index: {1}", m_Name, buffIndex);
	}
}

When GetBaseAddressAndBuffIndex complete the execution, the ring buffer slot is Unlock automatically, and when I call the function ProcessImage the buffer is already unlock.
I would expected the buffer being locked and not automatically unlocked by the .Net framework.

Hi @andrea.annovi,

sorry, that will be again a bit longish :innocent:: first let me clear up a common misconception about the IRingBuffer:

The acquisition engine locks the RingBufferImages, not the Stream.Wait methods.

In fact, the Stream.Wait methods .Unlock() the RingBufferImages in RingBufferLockMode.Auto (the old image). The ring buffer is filled by the acquisition engine from another thread (not accessible to you). The Stream.Wait methods just let you sync to the next acquired image. Although this concept is implemented in :cvb: much longer than .Net’s Tasks exist, you can imagine it as a combined call of Task.Wait() and Task.Result. The Action in the Task takes care of retrieving the correct buffer and doing the .Unlock() handling if needed.

Regarding the unlocked buffer

For RingBufferImages .Dispose() is an alias of .Unlock(). This is like .Net’s Stream.Close() method. This is done as it is unsafe to access unlocked buffers (this can lead to very hard to debug bugs). In your code you run a processing task on a RingBufferImage that is scoped in a using-block. The scope exit of this block calls .Dispose() on it. As the processing task will very probably still run after the scope exit (or even start after it), you work on unlocked RingBufferImages.

I suppose that is why you chose the more complex route to get the RingBufferImage from the Stream.RingBuffer instead of directly using the returned one from .WaitAsync. Via that you can still get unsafe access to the buffers.

For async processing you need to handle the .Unlock()ing manually:

public async Task StreamAnd(Action<RingBufferImage> action, CancellationToken token)
{
  m_stream.RingBuffer.LockMode = RingBufferLockMode.On;
  m_stream.Start();
  try
  {
    await AcquireImagesAnd(action, token);
  }
  finally
  {
    m_stream.Stop();
  }
}

private async Task AcquireImagesAnd(Action<RingBufferImage> action, CancellationToken token)
{
  var tasks = new List<Task>();

  while(!token.IsCancellationRequested)
  {
    var image = await m_stream.WaitAsync();
    tasks.Add(Task.Run(() => 
    {
      try
      {
        action(image);
      }
      finally
      {
        image.Unlock();
      }
    }));
  }

  await Task.WhenAll(tasks);
}

And combined with your processing:

var tcs = new CancellationTokenSource();
var processingTask = StreamAnd(image =>
{
  var imageData = image.Planes[0].GetLinearAccess();

  // process it

}, tcs.Token);

await Task.Delay(TimeSpan.FromSeconds(3));
tcs.Cancel();
await processingTask;

Everything in the action can be synchronous as it is run in the task pool anyways.

1 Like

Thank you, indeed the problem was in the RingBufferImage that is scoped in a using-block.

Now it works well.

Is there a way to retrieve the image ID of the acquired image (similar to the timestamp), using the new .Net API? I know it is possible with the C-API.

It should be in the RingBufferImage.Tag.

You can also combine the C-Api with the new .Net-Api: Just use the Device.Handle when you would have used the drivers’s IMG.