I am trying to use the SerialPort class in .net.
I've opted to keep my service async, so I am using the async-methods on SerialPort.BaseStream.
In my async method, I write a byte[] to the serial port, then start reading until I haven't received any more data in n milliseconds, and return that result.
The problem is, however, that I seem to miss the first byte in all replies other than the very first reply after opening the serial port.
If I close the port after every response (Read), and open it again before doing a new request (Write), the first byte is not missing. This, however, often results in a "Access to the port 'COM4' is denied." exception, if I try to open the port too soon after closing. It also seems very unnecessary to open/close for every write/read.
This is basically what my method looks like:
private async Task<byte[]> SendRequestAsync(byte[] request)
{
// Write the request
await _serialPort.BaseStream.WriteAsync(request, 0, request.Length);
var buffer = new byte[BUFFER_SIZE];
bool receiveComplete = false;
var bytesRead = 0;
// Read from the serial port
do
{
var responseTask = _serialPort.BaseStream.ReadAsync(buffer, bytesRead, BUFFER_SIZE - bytesRead);
if (await Task.WhenAny(responseTask, Task.Delay(300)) == responseTask)
{
bytesRead += responseTask.Result;
}
else
receiveComplete = true;
} while (!receiveComplete);
var response = new byte[bytesRead];
Array.Copy(buffer, 0, response, 0, bytesRead);
return response;
}
Is there anything obviously wrong in the way I am doing this? Is there a smarter way to achieve the same asynchronously?
Just because you're not observing the last ReadAsync() doesn't mean it gets canceled, it's still running, which apparently manifests by it reading the first byte of the following message.
What you should do is to cancel the last ReadAsync() by using a CancellationToken. Note that there is a possible race between the timeout and the read, but I'm assuming that if the timeout elapsed, it's not possible for the read to complete without another write.
The code would look like this:
var cts = new CancellationTokenSource();
do
{
var responseTask = _serialPort.BaseStream.ReadAsync(
buffer, bytesRead, BUFFER_SIZE - bytesRead, cts.Token);
if (await Task.WhenAny(responseTask, Task.Delay(300)) == responseTask)
{
bytesRead += responseTask.Result;
}
else
{
cts.Cancel();
receiveComplete = true;
}
} while (!receiveComplete);
Note that both the cause and the solution are my guesses, it's certainly possible that I'm wrong about one or both of them.
Related
I have this code that is asynchronously splitting a file into parts, and downloading them using HTTP content range. It then writes the downloaded data to a ViewStream on a Memory Mapped file. I am currently reading from the response stream into a buffer, then writing all the data from the buffer into the ViewStream. Is there a more efficient/faster way to do this? I am not really concerned about memory use, but I am trying to maximize speed. Pieces is a list that contains value tuples indicating the (Start, End) for the piece of the file, and httpPool is a object pool with a bunch of preconfigured HTTP Clients. Any help is greatly appreciated, thank you!
await Parallel.ForEachAsync(pieces,
new ParallelOptions() { MaxDegreeOfParallelism = Environment.ProcessorCount },
async (piece, cancellationToken) =>
{
//Get a http client from the pool and request for the content range
var client = httpPool.Get();
var request = new HttpRequestMessage { RequestUri = new Uri(url) };
request.Headers.Range = new RangeHeaderValue(piece.Item1, piece.Item2);
//Request headers so we dont cache the file into memory
if (client != null)
{
var message = await client.SendAsync(request,HttpCompletionOption.ResponseHeadersRead,cancellationToken).ConfigureAwait(false);
if (message.IsSuccessStatusCode)
{
//Get the content stream from the message request
using (var streamToRead = await message.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false))
{
//Create a memory mapped stream to the mmf with the piece offset and size equal to the response size
using (var streams = mmf.CreateViewStream(piece.Item1,message.Content.Headers.ContentLength!.Value,MemoryMappedFileAccess.Write))
{
//Copy from the content stream to the mmf stream
var buffer = new byte[bufferSize];
int offset, bytesRead;
// Until we've read everything
do
{
offset = 0;
// Until the buffer is very nearly full or there's nothing left to read
do
{
bytesRead = await streamToRead.ReadAsync(buffer.AsMemory(offset, bufferSize - offset),cancellationToken);
offset += bytesRead;
} while (bytesRead != 0 && offset < bufferSize);
// Empty the buffer
if (offset != 0)
{
await streams.WriteAsync(buffer.AsMemory(0, offset),cancellationToken);
}
} while (bytesRead != 0);
streams.Flush();
streams.Close();
}
streamToRead.Close();
}
}
message.Content.Dispose();
message.Dispose();
}
request.Dispose();
httpPool.Return(client);
});
I don't know how much it is going to help, but I tried to do something. How well does it work?
I also did some refactoring, so here are some notes:
Do not call .Close() or .Dispose() manually if you already have a using block or a using statement. All it does is add noise to your code and confuse anyone reading it. In fact, almost never call .Close() or .Dispose() manually at all.
Do you realize client would never be returned to the pool if any exception occurred in the method? You need to do these things in a finally block or by using an IDisposable struct which returns client to the pool in it's Dispose() implementation. (also, request would not be disposed in the method if any exception occurred, add using)
Whenever you can, prefer if statements that return early rather than ones that wrap the entire rest of the method. The latter is hard to read and maintain.
You are not really benefiting from Parallel as 99% of the method is asynchronously waiting for IO. Just use Task.WhenAll() instead.
I got rid of the custom buffering/copying and just called the CopyToAsync() method on message.Content which accepts a Stream. It should help the performance, probably. I reckon it has to be better optimized than the simplest possible buffer thingee.
Code:
await Task.WhenAll(pieces.Select(p => DownloadToMemoryMappedFile(p)));
// change the piece type from dynamic to what you need
async Task DownloadToMemoryMappedFile(dynamic piece, CancellationToken cancellationToken = default)
{
//Get a http client from the pool and request for the content range
var client = httpPool.Get();
try
{
using var request = new HttpRequestMessage { RequestUri = new Uri(url) };
//Request headers so we dont cache the file into memory
request.Headers.Range = new RangeHeaderValue(piece.Item1, piece.Item2);
if (client is null)
return;
using var message = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
if (!message.IsSuccessStatusCode)
return;
//Create a memory mapped stream to the mmf with the piece offset and size equal to the response size
using var streams = mmf.CreateViewStream(piece.Item1, message.Content.Headers.ContentLength!.Value, MemoryMappedFileAccess.Write);
await message.Content.CopyToAsync(streams).ConfigureAwait(false);
}
finally
{
httpPool.Return(client);
}
}
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 have this code to connect to streaming server. the server disconnect from time to time and I want to detect it and restart the connection when neede.
How can I detect in this code any kind of exceptions?
Because now I get disconnected and can't catch it.
this.ns = new NetworkStream(server);
while (true)
{
// Incoming message may be larger than the buffer size.
do
{
byte[] myReadBuffer = new byte[10240 * 5];
await this.ns.ReadAsync(myReadBuffer, 0, myReadBuffer.Length).ContinueWith((numberOfBytesRead) =>
{
string message = Encoding.ASCII.GetString(myReadBuffer, 0, numberOfBytesRead.Result);
p.Invoke(message);
});
}
while (this.ns.DataAvailable);
}
Your variable numberOfBytesRead is actually the previous task that has finished from where you can check whether it is completed or failed.
if(numberOfBytesRead.IsFaulted)
{
var aggregatedEx = numberOfBytesRead.Exception;
//do something
}
I have an SSL connection to a server and post requests to it. The act of posting a message should be instant with no delay to read the response, because there would be consequent posts that should come without the delay.
That's why i just do
this.sslStream.Write(byteArray, 0, byteArray.Length);
However, I need some responses to be actually received by my app, so I have a parallel thread:
this.threadReadStream = new Thread(new ThreadStart(this.ThreadReadStream));
this.threadReadStream.Start();
In that thread I have a loop which reads data from sslStream:
public void ThreadReadStream()
{
int bufferLen = 4096, byteCount, pos, pos2;
var buffer = new byte[bufferLen];
string responseBuffer = "", responsePart, response = "";
bool err = true;
while (true)
{
err = true;
byteCount = 0;
while (err)
{
err = false;
byteCount = 0;
try
{
byteCount = this.sslStream.Read(buffer, 0, bufferLen);
}
catch (Exception exception)
{
err = true;
this.TcpConnect();
}
}
if (byteCount > 0)
{
// do something
}
}
}
The problem is that sslStream.Read always returns 0 while being used in separate thread, but works fine if called in the same thread with sslStream.Write.
Few comments about the things that are not likely to influece the problem, however, it is better to clarify the stuff:
1) I use while(err) cycle to check that the connection was not broken. If so, I reconnect and read again.
2) For the ones who don't like while(true) thing. Yes, I have the exit condition from the loop in the parallel thread which shut downs all the tcp-related threads.
How does the server identify that the write process was ended?
Maybe there is actually a data on the way to the server so it don't starting to send the response.
In addition, sharing a TCP connection between different threads is not a good practice.
Let's say I want to have a function which reads data from the SerialPort
and returns a byte[].
public byte[] RequestData(byte[] data)
{
//See code below
}
Something as simple as this really doesn't work/perform well and isn't very reliable:
byte[] response = new byte[port.ReadBufferSize];
port.Open();
port.Write(data, 0, data.Length);
Thread.Sleep(300); //Without this it doesn't even work at all
Console.WriteLine("Bytes to read: {0}", port.BytesToRead);
int count = port.Read(response, 0, port.ReadBufferSize);
Console.WriteLine("Read {0} bytes", count);
port.Close();
port.Dispose();
return response.GetSubByteArray(0, count);
I also tried replacing the Thread.Sleep with something like:
while (port.BytesToRead < 14)
{
//Maybe Thread.Sleep(10) here?
}
But that causes problems to. (PS: I know I need at least 14 bytes)
Of course a better way (I think) would be to have something like:
port.ReceivedBytesThreshold = 14;
port.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived);
port.Open();
port.Write(data, 0, data.Length);
And then having a handler of course:
void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
var port = (SerialPort)sender;
while (port.BytesToRead > 0)
{
//Read the data here
}
}
But then I can't return the data as the result of the function I wanted to define?
The client code using this would have to subscribe to an event raised by this code,
but then how would it know the response is really the response to the request it just made.
(Multiple messages might be sent, and I can imagine one message taking longer to process on the other side than the other, or something).
Any advise would be welcome
UPDATE
The following code works a lot better, but if I remove the Thread.Sleep() statements it once again stops working properly. For example, the serial port monitoring tool clearly indicates 17 bytes have been written on the serial line. The first time BytesToRead = 10 and the next time BytesToRead = 4 , but then BytesToRead remains 0 so where did the last 3 bytes go to ?
void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
Thread.Sleep(100);
while (port.BytesToRead > 0)
{
Console.WriteLine("Bytes to read: {0}", port.BytesToRead);
var count = port.BytesToRead;
byte[] buffer = new byte[count];
var read = port.Read(buffer, 0, count);
if (count != read)
Console.WriteLine("Count <> Read : {0} {1}", count, read);
var collectAction = new Action(() =>
{
var response = dataCollector.Collect(buffer);
if (response != null)
{
this.OnDataReceived(response);
}
});
collectAction.BeginInvoke(null, null);
Thread.Sleep(100);
}
}
Here's how I've done it:
I have a wrapper for the class that accepts the vital data for the connection in the constructor and does the basic setup in that constructor. The consumer of the class calls a Connect method, which fires off another thread to perform the connection (non-blocking).
When the connection is complete, a StateEvent is fired indicating the connection is completed. At this time a Send queue is setup, a thread to work that queue is fired off and a read thread is also setup. The read thread reads 128 characters of data from the SerialPort, converts that to a string and then fires an event to pass along the received data. This is wrapped in a while thread that loops as long as the connection is maintained. When the consumer wants to send something, a Send method simply enqueues the data to be sent.
As far as knowing that the response is in response to something that was sent really isn't the job of a connection class. By abstracting away the connection into something as easy to handle as that, the consumer of the class can cleanly maintain the logic to determine if the response is what it expected.
Aren't serial ports fun. My only thought is that your fifo, assuming your device has one and its enabled, is being overrun.
Problem solved:
void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
var count = port.BytesToRead;
byte[] buffer = new byte[count];
var read = port.Read(buffer, 0, count);
var response = dataCollector.Collect(buffer);
if (response != null)
{
this.OnDataReceived(response);
}
}
It seems the problem wasn't actually this code but the code in the dataCollector.Collect() method.