通过async / await进行服务器通信?

时间:2014-03-26 13:33:54

标签: c# task-parallel-library async-await asp.net-4.5

我想通过async / await创建通过TAP发送Socket消息。

阅读this answerthis one后,我决定制作一个完整的样本:

所以我尝试了什么:

我从here获取了TAP扩展方法(一切正常):我在控制台cmd中测试它:

收件人代码

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();
}

发件人代码:

// 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();
    }

通知我从main中删除了async,因为我在控制台中测试它。

问题,

根据上面的链接,代码应该正常工作

但是,我没有例外,只是挂在那条线上:

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

(首先我运行接收器,然后运行发送器)

我错过了什么?

2 个答案:

答案 0 :(得分:4)

问题来自于“通知我从主程序中删除了异步,因为我在控制台中测试它。”。

在执行下一步之前,您需要等待操作完成。您作为示例使用的代码会在每个await处暂停以完成操作,您的代码将直接执行。

您可以通过在每个具有.Wait()的操作之后放置await或通过Task.Run(在线程池线程中运行此函数来解决此问题。但我认为最好知道何时应该使用异步,何时不应该。

当你有其他可以让线程做的工作时,应该使用异步,非常普遍的是“其他工作”将是在WinForms项目上处理UI消息或接受新连接的事情。一个ASP.NET站点。在控制台应用程序中,程序在等待时没有其他工作,因此在这种情况下使用函数的同步版本会更合适。


P.S。在我发布“之后我发表了评论,这就是为什么我删除异步等待并使用了Task.result ”,只是因为你知道从来没有永远 1 合并使用await的代码和阻止同步竞赛的代码(使用Task.ResultTask.Wait()等内容,您可能会导致代码死锁并停止运行!

这不是您当前示例的问题,因为控制台应用程序没有同步上下文,但如果您将此代码复制并粘贴到可以轻松锁定程序的内容中。

1:好的,您可以将await与阻止代码结合起来,但是您需要遵循规则,但如果您足够了解我所说的内容,那么您应该知道我已经足够安全地执行此操作。如果您不知道如何安全地执行此操作,请避免这样做

答案 1 :(得分:1)

因为你没有等待线程完成他们的工作,然后调用s.Close()套接字将在任何流量发送之前关闭。 您必须删除s.Close()电话或等待电话完成,例如通过

 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();

或者您可以在方法中将其打包并等待该方法完成。最终结果是在完成之前的呼叫之前不要呼叫Close

 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"));
}