Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to read data from stream asychronously in background

I would like to read data from a stream (serial, tcp, whatever) asynchronously and trigger events to notify other components.

Following is pseudo code, assuming "stream" is a valid stream.

public class Reader {

    private _stream;
    private _buffer = new bytes[4096];

    public event Action<byte[], int> DataRecieved;

    public async void StartReading() {
        while (true) {
            var nbytes = await _stream.ReadAsync(_buffer, 0, _buffer.Length);
            if (nbytes == 0)
                return;
            var handlers = DataRecieved;
            if (handlers != null)
                DataRecieved(_buffer, nbytes);
        }
    }

}

And the caller part:

var r = new Reader();
r.OnDataRecieved += myHandler;
r.StartReading();

I'm not sure doing something like this is a good idea. I read that using asynchonous void functions is not a good idea, but here I don't want caller to wait for the result, I want to notify it when some data is available.

What's the good way to do something like that ?

like image 816
Thibaut D. Avatar asked Dec 13 '25 18:12

Thibaut D.


2 Answers

void async is only considered to be used for GUI event handlers. In WinForms, events have all delegate-types of type void. Usually, you want, when using async , notify your caller when you have finished - in an asynchronous way. The .NET message-loop is considered the exception here, since you have no different possibility to use async.

In your case, the async/await keywords won't make much sense. I'd recommend to invoke your method using a Task or the ThreadPool (or BackgroundWorker).

You do not have a long running task on which you want to react in a asynchronous manner, but a parallel background-task, which should be used as such.

The idea of async/await is that the caller of a method continues after the method invocation and may execute code inside the method behind an await later. But that requires you to use await in the calling-method, which would block your main-thread.

Long story short: You have no other chance as using a second thread and use thread-synchronization.

Invoke is nothing else as placing a delegate in a queue which the message-loop reads and executes. In your case, you could do something similar: Take the read data, like the byte[] and put that in a queue (via the event). And whenever your main-thread desires to do some work, he grabs an item from the queue. That is one option. The best solution for this issue depends strongly on your application, and as far as you didn't tell us more details about the design, I can't recommend the best way. But async/await won't be it. Definitely.

like image 98
Patrik Avatar answered Dec 16 '25 09:12

Patrik


Make the method return Task to avoid async void. You can ignore that task if you want but eventually you probably want to wait for it to complete.

Also handle errors. Right now they are thrown away and the reading stops silently. You will never find bugs this way.

Wrap everything in Task.Run to make sure that this async method really runs completely asynchronously. Right now if each await completes right away this method will never return or not return in a long time. Don't risk that. Alternatively you can place await Task.Yield(); at the first line of the method.

like image 33
usr Avatar answered Dec 16 '25 10:12

usr



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!