ygoe / AsyncTcpClient

An asynchronous variant of TcpClient and TcpListener for .NET Standard.
153 stars 36 forks source link

How to wait for a transmission's completion? #9

Open artshade opened 4 years ago

artshade commented 4 years ago

Dear Developer,

Thank you for this project. Although, for example, we have a code where:

... // Somehere in AsyncTCPClient's initialization
ReceivedCallback = (c, count) =>
{
    Console.WriteLine("2");
    response = c.ByteBuffer.Dequeue(count);
    if (responseLength + count <= int.MaxValue)
        responseLength += count;
    Console.WriteLine("3, responseLength = {0}", responseLength );
    return Task.CompletedTask;
}
...
ct1 = new CancellationTokenSource(1000); // 1000ms for a request timeout
t1 = this.asyncTCPClient.Send(new ArraySegment<byte>(data, 0, data.Length), ct1.Token);
if (ct1.IsCancellationRequested) // If connection or any answer timeout
{
    Console.WriteLine("Timeout");
}
else
{
    ct1 = new CancellationTokenSource(1000); // 1000ms for a response timeour
    Console.WriteLine("1");
    t1 = this.asyncTCPClient.WaitAsync(ct1.Token); // Wait for any response available
    Console.WriteLine("4, responseLength = {0}", responseLength);
}

And the output would be:

    1
    4, responseLength = 0 // It seems that WaitAsync does not wait while ByteBuffer dequeues.
    2
    3, responseLength = 41

Is it possible to force WaitAsync to wait while the full transaction would be completed?

ygoe commented 4 years ago

The AsyncTcpClient.WaitAsync method waits for data to be available in the receive buffer. It doesn't wait until you processed it. If you want that, you should set a signal when you're done, at the end of your ReceivedCallback handler. You could use a TaskCompletionSource for that.

artshade commented 4 years ago

The AsyncTcpClient.WaitAsync method waits for data to be available in the receive buffer. It doesn't wait until you processed it. If you want that, you should set a signal when you're done, at the end of your ReceivedCallback handler. You could use a TaskCompletionSource for that.

Thank you, but how to wait for TaskCompletionSource if WaitAsync returns immediately when any data was received?

Currently, this one works:

ct1 = new CancellationTokenSource(1000);
this.asyncTCPClient.WaitAsync(ct1.Token); // Wait for any response available
if (!ct1.IsCancellationRequested) // If received something
{
    ct1 = new CancellationTokenSource(1000);
    while (responseLength != expectedResponseLength) // Wait for all requested data transferred
    {
        if (ct1.IsCancellationRequested) // If full data transfer timed out
        {
            fullDataTimeout= true; // Set flag of data's timeout
            break;
        }
        Thread.Sleep(100); // To escape a busy loop issue
    }
}
else
    connectionTimeout= true; // Set flag of connection's timeout

It works. However, it seems like it is not the best solution. Sorry, but what did you mean by TaskCompletionSource?

ygoe commented 4 years ago

You're calling AsyncTcpClient.WaitAsync and expect to wait until you finished processing the received data. That's the wrong method. You're waiting for new data to be available. Instead of this you should wait for your own event.

TaskCompletionSource<T> is a class that lets you create tasks you can wait on. Later, you can explicitly complete these tasks "from the outside", by calling a method like TrySetResult. Whoever waits for this task can then continue. It's to common pattern to asynchronously wait for something you do. It's also used in AsyncTcpClient.WaitAsync to wait for either new data or a closed connection.