Device.Net: Cancellation Tokens and Timeouts

Hi. May be will be good if you add CancellationTokenSource or just timeout for read\write operation. Something like this:

//One sec timeout by default
public int Timeout { get; set; } = 1000;

CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(Timeout);
await _ReadFileStream.ReadAsync(bytes, 0, bytes.Length, cancellationTokenSource.Token);

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 27 (17 by maintainers)

Commits related to this issue

Most upvoted comments

@datoml @skykharkov @fengqiangboy @fxjoos @mxjones

The library now supports cancellation tokens in the develop branch. It is mostly untested so I expect there to be issues. I will test when after work hours at some point soon.

Great to hear, that you implement it with an optional cancellation token. Is there a plan when it is open to try it out? 😃

@skykharkov @fxjoos @fengqiangboy

I’ve decided that I will add an optional cancellation token to the read/write methods. It makes a lot of sense. This way, you can handle timeouts without worrying about setting it at the constructor level and you can cancel whenever you like.

The only thing is that the onus will be on you to make sure that the device handle etc. is cleaned up afterwards.

In WindowsSerialPortDevice,I add a timeout parameter to Initialize function. It look like this

WindowsSerialPortDevice.Initialize:

private void Initialize()
        {
            _ReadSafeFileHandle = ApiService.CreateReadConnection(DeviceId, FileAccessRights.GenericRead | FileAccessRights.GenericWrite);

            if (_ReadSafeFileHandle.IsInvalid) return;

            var dcb = new Dcb();

            var isSuccess = ApiService.AGetCommState(_ReadSafeFileHandle, ref dcb);

            WindowsDeviceBase.HandleError(isSuccess, Messages.ErrorCouldNotGetCommState);

            dcb.ByteSize = _ByteSize;
            dcb.fDtrControl = 1;
            dcb.BaudRate = (uint)_BaudRate;
            dcb.fBinary = 1;
            dcb.fTXContinueOnXoff = 0;
            dcb.fAbortOnError = 0;

            dcb.fParity = 1;
            switch (_Parity)
            {
                case Parity.Even:
                    dcb.Parity = 2;
                    break;
                case Parity.Mark:
                    dcb.Parity = 3;
                    break;
                case Parity.Odd:
                    dcb.Parity = 1;
                    break;
                case Parity.Space:
                    dcb.Parity = 4;
                    break;
                default:
                    dcb.Parity = 0;
                    break;
            }

            switch (_StopBits)
            {
                case StopBits.One:
                    dcb.StopBits = 0;
                    break;
                case StopBits.OnePointFive:
                    dcb.StopBits = 1;
                    break;
                case StopBits.Two:
                    dcb.StopBits = 2;
                    break;
                default:
                    throw new ArgumentException(Messages.ErrorMessageStopBitsMustBeSpecified);
            }

            isSuccess = ApiService.ASetCommState(_ReadSafeFileHandle, ref dcb);
            WindowsDeviceBase.HandleError(isSuccess, Messages.ErrorCouldNotSetCommState);

            var timeouts = new CommTimeouts
            {
                WriteTotalTimeoutConstant = 0,
                ReadIntervalTimeout = 0,
                WriteTotalTimeoutMultiplier = 0,
                ReadTotalTimeoutMultiplier = 0,

                //------------------------------------------------------ this is timeout parameter
                ReadTotalTimeoutConstant = 500
            };

            isSuccess = ApiService.ASetCommTimeouts(_ReadSafeFileHandle, ref timeouts);
            WindowsDeviceBase.HandleError(isSuccess, Messages.ErrorCouldNotSetCommTimeout);
        }

In WindowsHidDevice, I add a timeout parameter to ReadReportAsync method like :

public async Task<ReadReport> ReadReportAsync()
        {
            byte? reportId = null;

            if (_ReadFileStream == null)
            {
                throw new NotInitializedException(Messages.ErrorMessageNotInitialized);
            }

            var bytes = new byte[ReadBufferSize];

            try
            {
                //---------------------------------------------------------- this is timeout parameter
                var task = Task.Delay(500);
                if (await Task.WhenAny(task, _ReadFileStream.ReadAsync(bytes, 0, bytes.Length)) == task)
                {
                    Close();
                    await InitializeAsync();
                    throw new TimeoutException();
                }
            }
            catch (Exception ex) when(!(ex is TimeoutException))
            {
                Log(Messages.ErrorMessageRead, ex);
                throw new IOException(Messages.ErrorMessageRead, ex);
            }

            if (ReadBufferHasReportId) reportId = bytes.First();

            var retVal = ReadBufferHasReportId ? RemoveFirstByte(bytes) : bytes;

            return new ReadReport(reportId, retVal);
        }

@fxjoos thanks for the detailed information. I’ll use this as I try to fix the issue.

@fengqiangboy I will try to do this.

I have implement SerialPort Device timeout handle and HID device timeout handle in my project, but I have no idea to implement this in other platform, I have not use in other pltform.

@fxjoos thanks for the detailed information. I’ll use this as I try to fix the issue.

@fengqiangboy I will try to do this.

@skykharkov @fxjoos @fengqiangboy

Is there a real need for cancellation tokens?

If each device had working timeouts used from the constructor, would there still be a need for cancellation tokens?

Timeouts are obviously necessary but if they are implemented, aren’t cancellation tokens redundant?

Yes, I agree with you. If timeouts are available then cancellation tokens are probably not needed.

@MelbourneDeveloper , yes you are right if timeouts are available then cancellation tokens are probably not needed.

To test the API with my reader (a Motorola/Symbol DS9208 barcode reader), I created a WPF test app with 2 buttons, one to start listening and one to stop.

Here is the code for timeout in WindowsHidApiService class:

public Stream OpenRead(SafeFileHandle readSafeFileHandle, ushort readBufferSize)
{
    return new FileStream(readSafeFileHandle, FileAccess.Read, readBufferSize, false)
    {
        ReadTimeout = 5000,
    };   
}

A System.InvalidOperationException is thrown as soon as the code reach this constructor with the message:

Timeouts are not supported on this stream.

I also tried with a CancellationToken instead. Test app code is as following:

private CancellationTokenSource _cancellationTokenSource;

private async void ButtonStartListening_Click(object sender, RoutedEventArgs e)
{
	try
	{
		if (_cancellationTokenSource==null)
		{
			_cancellationTokenSource=new CancellationTokenSource();
		}

	    readResult = await _device.WriteAndReadAsync(new byte[0], _cancellationTokenSource.Token);

	    if (_cancellationTokenSource.Token.IsCancellationRequested)
	    {
	        return null;
	    }

	    if (readResult.BytesRead > 0 && readResult.Data!=null)
	    {
	        dataRead = UTF8Encoding.Default.GetString(readResult.Data, 0, (int)readResult.BytesRead).Trim();

	        if (_logger.IsDebugLogEnabled())
	        {
	            _logger.LogDebug("data received: {0}", dataRead);
	        }
	    }
	}
	catch (Exception ex)
	{
	    dataRead = null;
	    _logger.LogError(ex, "StartListening");
	}
}

Where “_device” is an IDevice (hid).

While the code is “awaiting” the WriteAndReadAsync, I call _cancellationTokenSource.Cancel() from another method triggered by a second button:

private void ButtonStopListening_Click(object sender, RoutedEventArgs e)
{
    if (_device == null || _cancellationTokenSource==null)
    {
        return;
    }

    try
    {
        _cancellationTokenSource.Cancel();
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "CancelListening");
    }

    _cancellationTokenSource=null;
}

The WriteReadAsync method in DeviceBase:

public async Task<ReadResult> WriteAndReadAsync(byte[] writeBuffer, CancellationToken cancellationToken)
{
    await _WriteAndReadLock.WaitAsync();

    try
    {
        await WriteAsync(writeBuffer, cancellationToken);
        var retVal = await ReadAsync(cancellationToken);
        Log(Messages.SuccessMessageWriteAndReadCalled);
        return retVal;
    }
    catch (Exception ex)
    {
        Log(Messages.ErrorMessageReadWrite, ex);
        throw;
    }
    finally
    {
        _WriteAndReadLock.Release();
    }
}

Then in WindowsHIDDevice class for read method:

public override async Task<ReadResult> ReadAsync(CancellationToken cancellationToken)
{
    var data = (await ReadReportAsync(cancellationToken)).Data;
    Tracer?.Trace(false, data);
    return data;
}

public async Task<ReadReport> ReadReportAsync(CancellationToken cancellationToken)
{
    byte? reportId = null;

    if (_ReadFileStream == null)
    {
        throw new NotInitializedException(Messages.ErrorMessageNotInitialized);
    }

    var bytes = new byte[ReadBufferSize];

    try
    {
        await _ReadFileStream.ReadAsync(bytes, 0, bytes.Length, cancellationToken);
    }
    catch (Exception ex)
    {
        Log(Messages.ErrorMessageRead, ex);
        throw new IOException(Messages.ErrorMessageRead, ex);
    }

    if (ReadBufferHasReportId)
    {
        reportId = bytes.First();
    }

    var retVal = ReadBufferHasReportId ? RemoveFirstByte(bytes) : bytes;

    return new ReadReport(reportId, retVal);
}

When the Cancel() method is called on the CancellationTokenSource nothing happens, no logs, no trace.

Did I miss something is this code ?

@MelbourneDeveloper It is possible to cancel a read task now? Because our hid device sometime can’t response when it sleep time. And if I call Device.WriteAndReadAsync when device is sleep, It will waiting forever, and can’t send next data in other thread.

@fengqiangboy what case? If there is a problem, how do you know to cancel the task? Are you suggesting a timeout?

Some time the device not wrok correct, and not response. So we should cancel the read task, and tell user some error happen.