2
\$\begingroup\$

This is a USB related question, but the problem is not USB specific. When reading data from a USB endpoint, there are two patterns I am concerned with. Request/Response like HTTP (done over the Bulk endpoint), and Interrupt transfer where one would continuously poll the interrupt endpoint without writing data to the device.

In one particular case, there is a device that uses a request/response pattern but over the interrupt endpoint. This usually isn't a problem, but the UWP USB API does the polling for you on the interrupt endpoint and treats the incoming data as an event. So, if I want to implement a ReadAsync method, I need to await some kind of TaskCompletionSource to get the data. This is certainly not elegant but I'm looking for the least nasty way of synchronizing the context. I've used a combination of SemaphoreSlim and TaskCompletionSource here. Is there a more elegant way?

Code

private async void UsbInterruptInPipe_DataReceived(UsbInterruptInPipe sender, UsbInterruptInEventArgs args)
{
    try
    {
        await _DataReceivedLock.WaitAsync();

        var bytes = args.InterruptData.ToArray();
        _Chunks.Add(bytes);

        if (bytes != null)
        {
            Logger?.Log($"{bytes.Length} read on interrupt pipe {UsbInterruptInPipe.EndpointDescriptor.EndpointNumber}", nameof(UWPUsbInterfaceInterruptReadEndpoint), null, LogLevel.Information);
        }

        if (_ReadChunkTaskCompletionSource != null && _ReadChunkTaskCompletionSource.Task.Status!= TaskStatus.RanToCompletion)
        {
            //In this case there should be no chunks. TODO: Put some unit tests around this.
            //The read method wil be waiting on this
            var result = _Chunks[0];
            _Chunks.RemoveAt(0);
            _ReadChunkTaskCompletionSource.SetResult(result);
            Logger?.Log($"Completion source result set", nameof(UWPUsbInterfaceInterruptReadEndpoint), null, LogLevel.Information);
            return;
        }
    }
    finally
    {
        _DataReceivedLock.Release();
    }
}

And

public async Task<byte[]> ReadAsync()
{
    try
    {
        Logger?.Log($"Read called on {nameof(UWPUsbInterfaceInterruptReadEndpoint)}", nameof(UWPUsbInterfaceInterruptReadEndpoint), null, LogLevel.Information);

        await _ReadLock.WaitAsync();

        byte[] retVal = null;

        try
        {
            //Don't let any datas be added to the chunks here
            await _DataReceivedLock.WaitAsync();

            if (_Chunks.Count > 0)
            {
                retVal = _Chunks[0];
                Tracer?.Trace(false, retVal);
                _Chunks.RemoveAt(0);
                Logger?.Log($"Read the first chunk {nameof(UWPUsbInterfaceInterruptReadEndpoint)}", nameof(UWPUsbInterfaceInterruptReadEndpoint), null, LogLevel.Information);
                return retVal;
            }
        }
        catch(Exception ex)
        {
            Logger?.Log($"Error {nameof(ReadAsync)}", nameof(UWPUsbInterfaceInterruptReadEndpoint), ex, LogLevel.Error);
            throw;
        }
        finally
        {
            _DataReceivedLock.Release();                    
        }


        _ReadChunkTaskCompletionSource = new TaskCompletionSource<byte[]>();

        Logger?.Log($"Data received lock released. Completion source created. Waiting for data.", nameof(UWPUsbInterfaceInterruptReadEndpoint), null, LogLevel.Information);

        //Wait for the event here. Once the event occurs, this should return and the semaphore should be released
        retVal = await _ReadChunkTaskCompletionSource.Task;

        _ReadChunkTaskCompletionSource = null;

        Logger?.Log($"Completion source nulled", nameof(UWPUsbInterfaceInterruptReadEndpoint), null, LogLevel.Information);

        Tracer?.Trace(false, retVal);
        return retVal;
    }
    finally
    {
        _ReadLock.Release();
    }
}
\$\endgroup\$

closed as off-topic by dfhwze, Toby Speight, Grajdeanu Alex., Mast, Donald.McLean Jul 25 at 15:06

This question appears to be off-topic. The users who voted to close gave this specific reason:

  • "Lacks concrete context: Code Review requires concrete code from a project, with sufficient context for reviewers to understand how that code is used. Pseudocode, stub code, hypothetical code, obfuscated code, and generic best practices are outside the scope of this site." – dfhwze, Toby Speight, Grajdeanu Alex., Donald.McLean
If this question can be reworded to fit the rules in the help center, please edit the question.

  • 2
    \$\begingroup\$ You could post the code of the class that provides these 2 methods? We can only assume how you have declared and instantiated some of these threading classes like ReadLock. \$\endgroup\$ – dfhwze Jul 22 at 8:32
  • \$\begingroup\$ I did. It's the link that says "Code" \$\endgroup\$ – Melbourne Developer Jul 22 at 8:56
  • 4
    \$\begingroup\$ Links can rot or become broken, I would just add a bit more context in the question. Please include a description of the challenge here in your question. \$\endgroup\$ – dfhwze Jul 22 at 9:01
  • 4
    \$\begingroup\$ I realize that a link to the entire source code is provided, but it is much easier to provide a good review when the entire class is posted. \$\endgroup\$ – pacmaninbw Jul 22 at 15:09