I'm working on porting functionality from an example Windows Forms App to a Xamarin.Forms UWP app where it should write to & read from a bluetooth device on a COM port. I have it working fine most of the time, but intermittently the UWP app will get itself into a state where any call to dataReader.LoadAsync will trigger the exception:
Exception thrown at 0x74AF1A62 (KernelBase.dll) in MyApp.UWP.exe: WinRT originate error - 0x800710DD : 'The operation identifier is not valid.'.
Exception thrown: 'System.Runtime.InteropServices.COMException' in MyApp.UWP.exe
WinRT information: The operation identifier is not valid.
Restarting the app or Visual Studio does not help, the issue persists.
The last time it happened it did not appear to impact my dataWriter writing to the device, only the subsequent read.
All of the code is in the UWP project.
private DataReader _dataReader;
private DataWriter _dataWriter;
private SerialDevice _currentSerialDevice;
private async Task ReadAsync(SerialDevice serialDevice)
{
const uint ReadBufferLength = 1024;
if (_dataReader == null)
{
_dataReader = new DataReader(_currentSerialDevice.InputStream) { InputStreamOptions = InputStreamOptions.Partial };
}
uint bytesRead = await _dataReader.LoadAsync(ReadBufferLength); // <- exception here
if (bytesRead > 0)
{
var vals = new byte[bytesRead];
_dataReader.ReadBytes(vals);
DoStuffWithBytes(vals);
}
}
The serial device is chosen from a list in the application.
// Get serial devices
DeviceInformationCollection serialDeviceCollection = await DeviceInformation.FindAllAsync(SerialDevice.GetDeviceSelector());
// Load serial device from user choosing a device from serialDeviceCollection
public async void ConnectToSerialDevice(DeviceInformation device)
{
_currentSerialDevice = await SerialDevice.FromIdAsync(device.Id);
_currentSerialDevice.BaudRate = 115200;
_currentSerialDevice.Parity = SerialParity.None;
_currentSerialDevice.DataBits = 8;
_currentSerialDevice.StopBits = SerialStopBitCount.One;
_currentSerialDevice.Handshake = SerialHandshake.RequestToSend;
}
Code for writing to the device, which works even when it gets in the odd state:
private async Task WriteToDevice(byte[] outBuffer)
{
if (_currentSerialDevice != null)
{
if (_dataWriter == null)
{
_dataWriter = new DataWriter(_currentSerialDevice.OutputStream);
}
_dataWriter.WriteBytes(outBuffer);
await _dataWriter.StoreAsync();
}
}
I've tried things like flushing the data writer, recreating the datawriter & datareaders each time, but I get the same error nonetheless and cannot read anything from the device. In normal operation I am able successfully read the bytes I'm expecting (even when there are no bytes to be read, it "reads" 0 bytes) and can output this result with no exception.
The curious thing about it all is that not only does the original Windows Forms app work fine (with the same bluetooth device) even after it gets in this state, but just opening the port and reading from the device (in the old app) actually fixes the issue in the UWP app for a time, allowing me to read from the device again.
This may be related to asynchronous methods. You can try this:
var task = await _dataReader.LoadAsync(ReadBufferLength);
task.AsTask().Wait();
uint bytesRead = task.GetResults();
For asynchronous methods (such as DataReader.LoadAsync), events occur on the UI thread and can only be triggered once, and can only continue to be triggered after the previous asynchronous method is completed. Your question may be related to this.
In the end it turns out that the cause of the problem was the LoadAsync method hanging while waiting to fill the entire buffer (1024 bytes) despite the InputStreamOptions being set to Partial. The exception I was getting was somewhat unrelated and was to do with the asynchronous method not working properly (the method was being called again when the first task had not completed).
The fix was a combination of adding a ReadTimeout to the SerialDevice:
_currentSerialDevice.ReadTimeout = TimeSpan.FromMilliseconds(500);
and also wrapping the LoadAsync task itself in a timed cancellation token:
using (var cts = new CancellationTokenSource(500))
{
var task = _dataReader.LoadAsync(ReadBufferLength);
var readTask = task.AsTask(cts.Token);
uint bytesRead = await readTask;
}
This allowed the LoadAsync method to complete both when the device had less than 1024 bytes to consume (handled by the SerialDevice.ReadTimeout) and also when the device had 0 bytes to consume (handled by the CancellationToken).
I'm still not sure why running the win forms app fixed the issue for a time, possibly it was setting the ReadTimeout (while my UWP app was not) and this was persisting on the serial port in some way.
Related
I've written a small Winforms application in C# to load test an AWS websockets API that triggers a Lambda function. The application makes n calls to the API, with a given period, each submitting a randomised payload in the request. Different payloads result in different runtimes for the Lambda function (between a fraction of a second and several minutes).
Calling the API involves the following steps:
Connect
Send a message containing credentials, the route action and
the request payload (containing a small amount of data needed to
fulfil the request)
Receive the result
Disconnect
These steps are carried out in a Task which is added to a List<Task>. These tasks are then run using Task.WhenAll(taskList). Simplified (redacted) code is below. I'm completely prepared for people who know more than me to tell me it's terrible.
async Task RunTest()//Triggered by a button.
{
List<Task> taskList = new List<Task>();
for (int i = 0; i < numberOfRequests; i++)
{
//Generate inputPayload string.
taskList.Add(CallAPI(inputPayload, i, i * period));
}
await Task.WhenAll(taskList);
}
public async Task CallAPI(Dictionary<string, double> requestBody, int requestNumber, int delay)
{
if (requestNumber > 0) await Task.Delay(delay);//No need to delay the first one (although 'delay' is 0 on the first one anyway).
using (ClientWebSocket websocketClient = new ClientWebSocket())
{
CancellationToken cancellationToken = new CancellationToken();
await websocketClient.ConnectAsync(new Uri("wss://..."), cancellationToken);//Exception is thrown at this line after a random number of tasks.
InputStructure requestPayload = new InputStructure
{
Action = "RouteThatCallsLambda",
Name = nameTextBox.Text,
ApiKey = apiKeyTextBox.Text,
ApiRequestBody = requestBody
};
while (websocketClient.State == System.Net.WebSockets.WebSocketState.Open)
{
byte[] messageBuffer = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(requestPayload));
await websocketClient.SendAsync(new ArraySegment<byte>(messageBuffer), System.Net.WebSockets.WebSocketMessageType.Text, true, cancellationToken).ConfigureAwait(false);
break;
}
//All the 'record' bits do here is write stuff to a text box on the UI, and to a List<LogEntry> that I use to write out to a CSV file at the very end.
ArraySegment<byte> buffer;
System.Net.WebSockets.WebSocketReceiveResult receiveResult;
MemoryStream memoryStream;
while (websocketClient.State == System.Net.WebSockets.WebSocketState.Open)
{
buffer = new ArraySegment<byte>(new byte[8192]);
receiveResult = null;
memoryStream = new MemoryStream();
do
{
receiveResult = await websocketClient.ReceiveAsync(buffer, CancellationToken.None);
memoryStream.Write(buffer.Array, buffer.Offset, receiveResult.Count);
}
while (!receiveResult.EndOfMessage);
memoryStream.Seek(0, SeekOrigin.Begin);
if (receiveResult.MessageType == System.Net.WebSockets.WebSocketMessageType.Text)
{
StreamReader streamReader = new StreamReader(memoryStream, Encoding.UTF8);
string resultPayload = await streamReader.ReadToEndAsync();
//If successful, the payload will contain "validData".
if (resultPayload.Contains("validData"))
{
try
{
//Record the success.
}
catch
{
//Record the error (which in most cases would be a deserialisation exception).
}
await websocketClient.CloseAsync(System.Net.WebSockets.WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
}
else if (resultPayload.Contains("ping"))
{
//Ignore - the Lambda function sends a message for long-running requests to keep the connection alive.
}
else //Failed.
{
//Record the error message sent by the Lambda function.
await websocketClient.CloseAsync(System.Net.WebSockets.WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
}
}
break;
}
if (websocketClient.State == System.Net.WebSockets.WebSocketState.Closed)
{
//Record the connection closure.
}
}
if (requestNumber == numberOfRequests - 1)
{
//Record process complete.
}
}
The most I've ever set numberOfRequests to is 100 but it never gets that far before websocketClient.ConnectAsync() throws an 'unable to connect to the remote server' exception. In the CloudWatch API log stream, it reports 'Method completed with status: 410' which does suggest a client-side issue, but why it would strike at random I don't know.
Usually it gets to between 60 and 80 but sometimes after only a handful. Because it seems to be random, sometimes if I set numberOfRequests to much fewer it runs successfully all the way through. I've never seen any problems when I've set it to 1.
Does anyone have any idea what's going on?
Update:
[I originally posted the following as an answer to my own question, but it appears that all it's done is make the exception rarer. I have no idea why that would be the case.]
It appears I've solved it. I saw on a couple of websites the following way of doing things but I didn't think it would make any difference. However, on the basis that I already had an inkling that the problem was due to some strange threading issue, I gave it a go anyway.
I moved the two while (websocketClient.State == System.Net.WebSockets.WebSocketState.Open) blocks into their own separate async Tasks, one for sending the message and one for receiving the result. Then immediately after websocketClient.ConnectAsync() I await a call to each in turn, passing the necessary parameters:
await websocketClient.ConnectAsync(new Uri("wss://..."), CancellationToken.None);
await SendMessage(websocketClient, requestBody);
await ReceiveMessage(websocketClient);
TLDR: When setting up a websocket API on AWS, use mock endpoints for the connect and disconnect routes, not Lambda functions, to return the 200 response.
Solved it. It seems the 'unable to connect' errors were a secondary consequence of how I'd set up the websocket API routes.
Having followed an article on this, I'd set up two Lambda functions that return a 200 response, that serve as the endpoints for the $connect and $disconnect websocket routes. These contain one line of Javascript that returns {"statusCode":200}.
What seems to have been happening is that when the 'connect' Lambda function was invoked it was returning a 'rate exceeded' error instead of a 200 response, which appears to the client as 'unable to connect'.
The solution: dispense with the Lambda functions altogether and use mock endpoints that pass through a template containing the 200 response instead.
So now I'm always able to connect because I don't need to invoke a Lambda function just to create a websocket connection. Instead, the 'rate exceeded' error occurs when the message is sent to invoke the function that does the actual data processing, and it's much more obvious what's going on.
Fundamentally, the problem seems to be that Lambda concurrency is only set to 10, despite the documentation stating that the default is 1,000. I'm now in a better position to evidence a request for an increase.
I have an app that communicates to some hardware over rfcomm on bluetooth. My app works on Android and am in the process of getting things working on UWP. Here's how I set up the stream reader/writers in the UWP code:
var btDevice = await BluetoothDevice.FromIdAsync(devId);
var services = await btDevice.GetRfcommServicesAsync();
if (services.Services.Count > 0)
{
// We only have one service so use the first one...
var service = services.Services[0];
// Create a stream...
_bluetoothStream = new StreamSocket();
await _bluetoothStream.ConnectAsync(service.ConnectionHostName, service.ConnectionServiceName);
_dataReader = new DataReader(_bluetoothStream.InputStream);
_dataWriter = new DataWriter(_bluetoothStream.OutputStream);
_dataReader.InputStreamOptions = InputStreamOptions.Partial;
My hardware only sends data to my app after the app sends it data so I've set up a send/receive mechanism. Everything works great except for a specific use case where my device is restarting (but bluetooth connection is still active) and is unable to send a response. In this case my upper level code is setup to attempt a retry, however the bluetooth connection gets closed when the receive times out.
_dataWriter.WriteBytes(comm.TransmitData);
Task<UInt32> writeAysncTask = _dataWriter.StoreAsync().AsTask();
UInt32 bytesWritten = await writeAysncTask;
:
try
{
using (var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(comm.TimeoutMs))) // _receiveTimeoutMs)))
{
// When this times out, exception gets thrown and socket is closed
// How do I prevent the socket from closing so I can do a retry???
var loadTask = _dataReader.LoadAsync(comm.ReceiveCount).AsTask(cts.Token);
bytesRead = await loadTask;
if (bytesRead > 0)
{
rxData = new byte[bytesRead];
_dataReader.ReadBytes(rxData);
}
else
{
System.Diagnostics.Debug.WriteLine("Received 0!");
}
}
}
catch (Exception ex)
{
// The bluetooth connection is closed automatically if the
// caancellationToken fires...In my case, I need the connection
// to stay open...How do I achieve this???
// Update: When this code is executed with _dataReader/Writer
// that was created with SerialDevice class (see below), the
// timeout exception does not cause the Serial connection to
// close so my calling code can then issue a retry.
System.Diagnostics.Debug.WriteLine(ex.Message) ;
}
UPDATE: It should be noted that when I use the exact same code with streams created from a SerialDevice everything works as I would expect...When the receive times out the socket is NOT closed. Seems like maybe I'm up against something in the Bluetooth Implementation in UWP. Ugh. Here's how I create the _dataReader/_dataWriter with the SerialDevice class:
_serialDevice = await SerialDevice.FromIdAsync(devId);
// Configure the port
_serialDevice.BaudRate = _baudrate;
_serialDevice.Parity = SerialParity.None;
_serialDevice.DataBits = 8;
_serialDevice.StopBits = SerialStopBitCount.One;
_dataReader = new DataReader(_serialDevice.InputStream);
_dataWriter = new DataWriter(_serialDevice.OutputStream);
I've come up with a work around to the problem I was facing. Unfortunately, I can't use the same code for SerialDevice and BluetoothDevice. I have to say, it really stinks that the bluetooth socket gets closed when a cancellation token times out. The code would be so much cleaner if it didn't close! Shouldn't it be up to me to decide if the socket should be closed? Now I'm stuck with this:
using (var cts = new CancellationTokenSource())
{
Task.Run(async () =>
{
try
{
await Task.Delay((int)comm.TimeoutMs, cts.Token);
System.Diagnostics.Debug.WriteLine("Canceling async read");
// If we make it this far, then the read as failed...cancel the async io
// which will cause the bytesRead below to be 0.
await _bluetoothStream.CancelIOAsync();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
}, cts.Token);
var loadTask = _dataReader.LoadAsync(comm.ReceiveCount).AsTask();
bytesRead = await loadTask;
if (bytesRead > 0)
{
// SIgnal the delay task to cancel...
cts.Cancel(true);
if (bytesRead > comm.ReceiveCount)
System.Diagnostics.Debug.WriteLine("Received too much!!");
rxData = new byte[bytesRead];
_dataReader.ReadBytes(rxData);
}
else
{
System.Diagnostics.Debug.WriteLine("Received 0!");
}
}
After implementing this, I did notice that after I've paired my BT device, Windows returns it as a SerialDevice in the following query:
string aqs = SerialDevice.GetDeviceSelector();
DeviceInformationCollection devices = await DeviceInformation.FindAllAsync(aqs);
// My bluetooth device is included in the 'devices' collection
So if I connect to it as a serial device, I guess I won't need the work around after all. Oh well, hopefully this post will help someone else.
I have a small UWP app which uses a StreamSocket. The socket is accessed via using the socket.InputStream.AsStreamForRead() method.
This works fine for nearly all sizes of incoming data (10 bytes to 6,000 bytes). But when using the overload with a buffer size the socket hangs when more data is received. So 6000 bytes are no longer received if the buffer is set to 4096. Even when reading the data in chunks of 10 bytes it does not work. The method ReadAsync hangs forever.
I am not sure if this is a bug. I would expect that I can still receive the data. If not I need to know the default size or behavior of that buffer.
Example code:
StreamSocket socket = InitSomewhere();
var readStream = socket.InputStream.AsStreamForRead(500);
var buffer = new byte[100]
readStream.ReadAsync(buffer, 0, 100) // Hangs here if received > 500!
Does anyone have an idea?
Best regards, Christan
Firstly, I can not reproduce this issue in my side using the official StreamSocket sample.
On the other hand, you can try to use the DataReader class to read the data as the above sample.
private async void OnConnection(
StreamSocketListener sender,
StreamSocketListenerConnectionReceivedEventArgs args)
{
DataReader reader = new DataReader(args.Socket.InputStream);
try
{
while (true)
{
// Read first 4 bytes (length of the subsequent string).
uint sizeFieldCount = await reader.LoadAsync(sizeof(uint));
if (sizeFieldCount != sizeof(uint))
{
// The underlying socket was closed before we were able to read the whole data.
return;
}
// Read the string.
uint stringLength = reader.ReadUInt32();
uint actualStringLength = await reader.LoadAsync(stringLength);
if (stringLength != actualStringLength)
{
// The underlying socket was closed before we were able to read the whole data.
return;
}
// Display the string on the screen. The event is invoked on a non-UI thread, so we need to marshal
// the text back to the UI thread.
NotifyUserFromAsyncThread(
String.Format("Received data: \"{0}\"", reader.ReadString(actualStringLength)),
NotifyType.StatusMessage);
}
}
I'm having an issue with writing to a serial device in UWP. My task for writing to the port looks like this:
public async Task WriteAsync(byte[] stream)
{
if (stream.Length > 0 && serialDevice != null)
{
await writeSemaphore.WaitAsync();
try
{
DataWriter dataWriter = new DataWriter(serialDevice.OutputStream);
dataWriter.WriteBytes(stream);
await dataWriter.StoreAsync();
dataWriter.DetachStream();
dataWriter = null;
}
finally
{
writeSemaphore.Release();
}
}
}
The code works fine the first two times I call this function. The third time I get Unhandled Exception in ntdll.dll in the await dataWriter.StoreAsync() line.
The full exception I can see is:
Unhandled exception at 0x00007FFCB3FCB2C0 (ntdll.dll) in xx.exe:
0xC000000D: An invalid parameter was passed to a service or function.
This answer mentions a garbage collector closing an input stream, however I don't see why would it happen in my code. Any help on getting to the bottom of this issue would be highly appreciated!
Turns out the solution to my problem was in another piece of code. I had a function reading the bytes like this:
private async Task ReadAsync(CancellationToken cancellationToken)
{
Task<UInt32> loadAsyncTask;
uint ReadBufferLength = 1024;
// If task cancellation was requested, comply
cancellationToken.ThrowIfCancellationRequested();
// Set InputStreamOptions to complete the asynchronous read operation when one or more bytes is available
dataReader.InputStreamOptions = InputStreamOptions.Partial;
using (var childCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
{
// Create a task object to wait for data on the serialPort.InputStream
loadAsyncTask = dataReader.LoadAsync(ReadBufferLength).AsTask(childCancellationTokenSource.Token);
// Launch the task and wait
UInt32 bytesRead = await loadAsyncTask;
if (bytesRead > 0)
{
byte[] vals = new byte[3]; //TODO:adjust size
dataReader.ReadBytes(vals);
//status.Text = "bytes read successfully!";
}
}
}
Specifically the problem was in the following two lines:
byte[] vals = new byte[3]; //TODO:adjust size
dataReader.ReadBytes(vals);
As soon as I set the size of the vals array to bytesRead value the problem went away.
Normally, you would not have to set dataWriter to null, because the GC will know that an object will not be used anymore.
You'd better call dataWriter.Dispose() method like many UWP samples.
For example: SocketActivityStreamSocket
Please read IDisposable.Dispose Method () document for more details.
I'm building a TCP server in UWP to receive and send data from an NodeMCU (Arduino based) to an windows PC (later it will be a Raspberry Pi with Windows 10 IOT).
When setting up and startig the server there is no problem, and also the NodeMCU is changing the status to "connected" - it seems that the connection between PC and NodeMCU works just fine.
When receiving the data, I get an exception in the event handler, becaust the size (variable size in the code below) of the package is read as "1414743380" which is way too much and so I get an exception "Insufficient memory to continue the execution of the program." at line "await reader.LoadAsync((uint)size);" which seems just right.
The variable payloadsize contains this:
The question is why do I get such a package size and where does it come from?
Is there something wrong on the NodeMCU / Arduino side?
I used the code for the TCP Server from the Microsoft Example.
Starting the server:
public async Task<bool> StartListeningAsync()
{
if (_localSocket == null)
{
_localSocket = new StreamSocketListener();
_localSocket.ConnectionReceived += LocalSocketConnectionReceived;
await _localSocket.BindServiceNameAsync(CommunicationPort);
return true;
}
return false;
}
Event handler for TCP messages:
private async void LocalSocketConnectionReceived(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args)
{
using (var reader = new DataReader(args.Socket.InputStream))
{
reader.InputStreamOptions = InputStreamOptions.None;
// Read the length of the payload that will be received.
byte[] payloadSize = new byte[(uint)BitConverter.GetBytes(0).Length];
await reader.LoadAsync((uint)payloadSize.Length);
reader.ReadBytes(payloadSize);
// Read the payload.
int size = BitConverter.ToInt32(payloadSize, 0);
byte[] payload = new byte[size];
await reader.LoadAsync((uint)size);
reader.ReadBytes(payload);
}
}
the message from the Arduino is this (from Wireshark):
EDIT: Full message description