了解C#控制台TCP服务器的异步行为

问题描述 投票:0回答:1

我想在.NET Core 2.0中使用异步创建一个简单的TCP服务器(因为根据我的理解,它比产生线程更合理)使用async/await方法(因为我认为它比使用IAsyncResult和更新的更新) *Begin/*End方法)。

我写了这个小服务器,它接受来自客户端的新连接,然后开始向它们发送100条消息(它们之间有1秒的延迟)。

主要问题是:

如果我没有产生新线程,那么服务器如何继续向多个客户端发送延迟消息,实际上它是“等待连接”?是否有任何隐藏的低级信号/事件,或者真的只是新的线程?

第二个问题是:

如果我没有使用这个全新的async Main语法糖而且我没有“等待”发送消息的async任务 - 我是否正确使用异步?

class Program
{
    public static void Main(string[] args)
    {
        StartServer();
    }

    public static void StartServer()
    {
        IPAddress localhost = IPAddress.Parse("127.0.0.1");
        TcpListener listener = new TcpListener(localhost, 5567);

        Console.WriteLine($"Starting listening on {listener.Server.LocalEndPoint}");
        listener.Start();
        while (true)
        {
            Console.WriteLine("Waiting for connection...");
            var client = listener.AcceptTcpClient(); // synchronous
            Console.WriteLine($"Connected with {client.Client.RemoteEndPoint}!");

            Console.WriteLine("Starting sending messages...");
            SendHundredMessages(client); // not awaited -- StartServer is not async
        }
    }


    public static async Task SendHundredMessages(TcpClient client)
    {
        var stream = client.GetStream();

        for (int i=0; i<100; i++)
        {
            var msg = Encoding.UTF8.GetBytes($"Message no #{i}\n");
            await stream.WriteAsync(msg, 0, msg.Length);    // returning back to caller?

            await Task.Delay(1000);     // and what about here?
        }
        client.Close();
    }
}

原始代码和下面的版本有什么区别? async Main有什么区别?

class Program
{
    public static async Task Main(string[] args)
    {
        await StartServer();
    }

    public static async Task StartServer()
    {
        IPAddress localhost = IPAddress.Parse("127.0.0.1");
        TcpListener listener = new TcpListener(localhost, 5567);

        Console.WriteLine($"Starting listening on {listener.Server.LocalEndPoint}");
        listener.Start();
        while (true)
        {
            Console.WriteLine("Waiting for connection...");
            var client = await listener.AcceptTcpClientAsync(); // does it make any difference when done asynchronously?
            Console.WriteLine($"Connected with {client.Client.RemoteEndPoint}!");

            Console.WriteLine("Starting sending messages...");
            SendHundredMessages(client); // cannot await here, because it blocks next connections
        }
    }


    public static async Task SendHundredMessages(TcpClient client)
    {
        var stream = client.GetStream();

        for (int i=0; i<100; i++)
        {
            var msg = Encoding.UTF8.GetBytes($"Message no #{i}\n");
            var result = stream.WriteAsync(msg, 0, msg.Length);

            await Task.Delay(1000);
            await result;
        }
        client.Close();
    }
}
c# tcp server async-await .net-core
1个回答
0
投票

主要问题的答案:

在后台,通常,对象使用一些api(winapi for windows)。这些api可以不同地实现异步。例如,事件(winapi事件)或回调。所以答案是肯定的 - 有隐藏的信号或线程。例如,您可以看到Ping类。 Ping.InternalSend使用ThreadPool.RegisterWaitForSingleObject执行异步任务。

关于async Main的答案:

在你的第一个代码中,因为StartServer不是异步的,所以Main方法在你的“接受”循环结束之前不会得到控制。

在你的第二个代码中,Main方法将返回控制,然后调用listener.AcceptTcpClientAsync。但是因为你使用await StartServer();,Main方法将等到StartServer结束。

一些代码可以解释:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace TestConsole
{
    class Program
    {
        static void Main(string[] args)
        {   
            // not await for exploration
            var task = StartServer();

            Console.WriteLine("Main StartServer finished");
            task.Wait();
            Console.WriteLine("task.Wait() finished");
            Console.ReadKey();
        }        

        static async Task StartServer()
        {
            Console.WriteLine("StartServer enter");

            for(int i = 0; i < 3; i++) {
                await Task.Delay(1000); // listener.AcceptTcpClientAsync simulation
                SendHundredMessages();
            }

            Console.WriteLine("StartServer exit");
        }

        static async Task SendHundredMessages()
        {
            Console.WriteLine("SendHundredMessages enter");

            await Task.Run(() => {
                Thread.Sleep(2000);
            });

            Console.WriteLine("SendHundredMessages exit");
        }        
    }
}

此代码生成此输出:

StartServer输入 主StartServer完成 SendHundredMessages输入 SendHundredMessages输入 SendHundredMessages退出 SendHundredMessages输入 StartServer退出 task.Wait()完成了 SendHundredMessages退出 SendHundredMessages退出

正如你所看到的,Main方法在第一次Task.Delay之后继续执行。

一个警告:

你不等SendHundredMessages结束,这是非常糟糕的。在示例输出中,您可以在“task.Wait()完成”后看到“SendHundredMessages结束”。在示例应用当然不是危险,但在实际项目中你可以得到大问题。

© www.soinside.com 2019 - 2024. All rights reserved.