Server communication via async/await?

2019-02-28 14:12发布

问题:

I want to create Socket message sending via TAP via async/await.

After reading this answer and this one - I decided to create a fully working sample :

So what have I tried :

I took the TAP extenstion methods from here (all ok) : and I test it in console cmd :

Reciever Code

public static class SocketExtensions
{
    public static Task<int> ReceiveTaskAsync(this Socket socket, byte[] buffer, int offset, int count)
    {
        return Task.Factory.FromAsync<int>(
                         socket.BeginReceive(buffer, offset, count, SocketFlags.None, null, socket),
                         socket.EndReceive);
    }

    public static async Task<byte[]> ReceiveExactTaskAsync(this Socket socket, int len)
    {
        byte[] buf = new byte[len];
        int totalRead = 0;
        do{
            int read = await ReceiveTaskAsync(socket, buf, totalRead, buf.Length - totalRead);
            if (read <= 0) throw new SocketException();
            totalRead += read;
        }while (totalRead != buf.Length);
        return buf;
    }

    public static Task ConnectTaskAsync(this Socket socket, string host, int port)
    {
        return Task.Factory.FromAsync(
                         socket.BeginConnect(host, port, null, null),
                         socket.EndConnect);
    }

    public static Task SendTaskAsync(this Socket socket, byte[] buffer)
    {
        return Task.Factory.FromAsync<int>(
                         socket.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, null, socket),
                         socket.EndSend);
    }
}
static   void  Main()
{
      Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
      s.ConnectTaskAsync("127.0.0.1", 443);


      var buf1 =    s.ReceiveExactTaskAsync(100); //read exactly 100 bytes
      Console.Write(Encoding.UTF8.GetString(buf1.Result)); 

      var buf2 =   s.ReceiveExactTaskAsync(100); //read exactly 100 bytes
      Console.Write(Encoding.UTF8.GetString(buf2.Result));

      Console.ReadLine();
}

Sender Code :

// use same extension method class like above ....^

   void  Main()
    {
     Socket s = new Socket(SocketType.Stream    , ProtocolType.Tcp);
     s.ConnectTaskAsync( "127.0.0.1" , 443);
     s.SendTaskAsync(Encoding.UTF8.GetBytes("hello"));

     s.Close();
     Console.ReadLine();
    }

notice I removed the async from main since im testing it in console.

Question ,

According to link above , the code should work

However I'm getting no exception and it's just hangs on that line :

Console.Write(Encoding.UTF8.GetString(buf1.Result));

(First I run receiver , then I run sender)

What am I missing?

回答1:

the problem comes from the "notice I removed the async from main since im testing it in console.".

You need to wait for the operation to complete before doing the next step. The code you used as an example pauses at each await for the operation to complete, your code just goes straight through.

You may be able to fix this by putting a .Wait() after each operation that would have had a await or by running this function inside a threadpool thread via Task.Run(, however I think it is better to know when you should use async and when you should not.

Async should be used when you have other work you could have the thread be doing, very commonly that "other work" will be things like processing UI messages on a WinForms project or accepting new connections on a ASP.NET site. In a console application there is no other work your program could be doing while it waits, so in that situation it would be more appropriate to use the synchronous version of the functions instead.


P.S. You made the comment after I posted "that's why I remove the async awaits and used Task.result", just so you know never ever1 combine code that uses await and code that blocks the synchronization contest (by using things like Task.Result or Task.Wait(), you will likely cause your code to deadlock and stop functioning!

It is not a issue for your current example because console applications do not have a synchronization context, but if you copied and pasted this code to something that did you could easily lock up your program.

1: Ok, you could combine await and blocking code but there are rules you need to follow, but if you know enough to dispute my what I am saying you know enough to safely do it. If you don't know how to safely do it just avoid doing it



回答2:

since you do not wait for the threads to do their work and then call s.Close() the socket will be closed before any traffic is sent out. You would have to either remove the s.Close() call or wait until the calls are complete, for instance via

 Task connect = s.ConnectTaskAsync( "127.0.0.1" , 443);
 connect.Wait(); // you have to connect before trying to send
 Task sendData = s.SendTaskAsync(Encoding.UTF8.GetBytes("hello"));
 sendData.Wait(); // wait for the data to be sent before calling s.Close()

 s.Close();

or you could box it in a method and Wait for that method to complete. The end result is to not call Close before completing the previous calls.

 void Main()
 {
     Socket s = new Socket(SocketType.Stream    , ProtocolType.Tcp);
     Task worker = DoWorkAsync(s);
     worker.Wait();
     s.Close();
     Console.ReadLine();
}

private async Task DoWorkAsync(Socket s){
     await s.ConnectTaskAsync( "127.0.0.1" , 443);
     await s.SendTaskAsync(Encoding.UTF8.GetBytes("hello"));
}