1

I am a farily new .NET-developer and I'm currently reading up on async/await. I need to work on a framework used for testing devices that are controlled by remotely accessing servers using TCP and reading/writing data from/to these servers. This will be used for unit tests.

There is no application-layer protocol and the server may send data based on external events. Therefore I must be able to continuously capture any data coming from the server and write it to a buffer, which can be read from a different context.

My idea goes somewhere along the lines of the following snippet:

// ...
private MemoryStream m_dataBuffer;
private NetworkStream m_stream;
// ...

public async void Listen()
{
  while (Connected)
  {
    try
    {
      int bytesReadable = m_dataBuffer.Capacity - (int)m_dataBuffer.Position;

      // (...) resize m_dataBuffer if necessary (...)

      m_stream.ReadTimeout = Timeout;

      lock (m_dataBuffer)
      {
        int bytesRead = await m_stream.ReadAsync(m_dataBuffer.GetBuffer(), 
          (int)m_dataBuffer.Position, bytesReadable);
        m_stream.Position += bytesRead;
      }
    }
    catch (IOException ex)
    {
      // handle read timeout.
    }
    catch (Exception)
    {
      throw new TerminalException("ReadWhileConnectedAsync() exception");
    }
  }
}

This seems to have the following disadvantages:

  1. If calling and awaiting the Listen function, the caller hangs, even though the caller must be able to continue (as the network stream should be read as long as the connection is open).
  2. If declaring it async void and not awaiting it, the application crashes when exceptions occur in the Task.
  3. If declaring it async Task and not awaiting it, I assume the same happens (plus I get a warning)?

The following questions ensue:

  • Can I catch exceptions thrown in Listen if I don't await it?
  • Is there a better way to constantly read from a network stream using async/await?
  • Is it actually sane to try to continuously read from a network stream using async/await or is a thread a better option?
moktor
  • 1,011
  • 1
  • 8
  • 12
  • Assuming you're using .Net framework 4.5, process should not catch when there is an unhandled exception in async method. Also what's the point in throwing exception from async void method(given that it can't be noticed)? – Sriram Sakthivel Nov 25 '14 at 13:02

2 Answers2

1

async void should at the very least be async Task with the return value thrown away. That makes the method adhere to sane standards and pushes the responsibility into the caller which is better equipped to make decisions about waiting and error handling.

But you don't have to throw away the return value. You can attach a logging continuation:

async Task Log(Task t) {
 try { await t; }
 catch ...
}

And use it like this:

Log(Listen());

Throw away the task returned by Log (or, await it if you wish to logically wait).

Or, simply wrap everything in Listen in a try-catch. This appears to be the case already.

Can I catch exceptions thrown in Listen if I don't await it?

You can find out about exceptions using any way that attaches a continuation or waits synchronously (the latter is not your strategy).

Is there a better way to constantly read from a network stream using async/await?

No, this is the way it's supposed to be done. At any given time there should be one read IO outstanding. (Or zero for a brief period of time.)

Is it actually sane to try to continuously read from a network stream using async/await or is a thread a better option?

Both will work correctly. There is a trade-off to be made. Synchronous code can be simpler, easier to debug and even less CPU intensive. Asynchronous code saved on thread stack memory and context switches. In UI apps await has significant benefits.

Community
  • 1
  • 1
usr
  • 168,620
  • 35
  • 240
  • 369
  • 1
    Your point about externally logging and handling exceptions by attaching the output to a continuation certainly helped me. – moktor Nov 26 '14 at 15:37
1

I would do something like this:

const int MaxBufferSize = ... ;
Queue<byte> m_buffer = new Queue<byte>(MaxBufferSize);
NetworkStream m_stream = ... ;

...

// this will create a thread that reads bytes from 
// the network stream and writes them into the buffer 
Task.Run(() => ReadNetworkStream());


private static void ReadNetworkStream()
{
    while (true)
    {
        var next = m_stream.ReadByte();
        if (next < 0) break; // no more data

        while (m_buffer.Count >= maxBufferSize)
            m_buffer.Dequeue(); // drop front

        m_buffer.Enqueue((byte)next);
    }
}
3dGrabber
  • 4,710
  • 1
  • 34
  • 42
  • 1
    So you would run an inherently IO bound operation on a threadpool thread? – Daniel Kelley Nov 25 '14 at 14:35
  • @DanielKelley From what I understand you are saying this is a bad idea. Is this because of his use of the blocking `ReadByte()` and the fact that context switches to the `ReadNetworkStream()` task will happen even though no data has been read? So the thread may burns CPU time without doing anything actually? – moktor Nov 26 '14 at 15:41
  • From what he's saying I understand that he dislikes that a thread from the threadpool is locked up forever. @DanielKelley: care to post descriptive/constructive comments? – 3dGrabber Nov 27 '14 at 08:09
  • "burns CPU time": No, ReadByte() blocks until data is available. No CPU cycles wasted. However there is one context switch per byte, even though the bytes might arrive in bulk. There are ways to avoid this at the cost of complexity. – 3dGrabber Nov 27 '14 at 08:22
  • sorry i just run into this thread and come up with one question while reading your codes. Won't there be any exception occurred when the function "ReadNetworkStream" is called twice continuously (such like button is clicked twice and trigger events), object "m_stream" is accessed by two threads (or 2 tasks), and thus an exception occurs since it is not threadsafe. Do I understand it right? – Jog Dan Dec 09 '17 at 04:24
  • If you need thread safety you can use a [ConcurrentQueue](https://learn.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentqueue-1). – 3dGrabber Dec 11 '17 at 08:11