简体   繁体   English

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

[英]Understanding async behaviour of C# console TCP server

I want to create a simple TCP server in .NET Core 2.0 using asynchrony (because from what I understand, it's more reasonable than spawning threads) with the async/await approach (because I believe it's more up-to-date than the one with IAsyncResult and *Begin/*End methods). 我想通过async/await方法(使用异步(因为据我所知,它比生成线程更合理))在async/await方法中在.NET Core 2.0中创建一个简单的TCP服务器(因为我认为它比使用async/await方法更为最新) IAsyncResult*Begin/*End方法)。

I've written this small server that accepts new connections from clients and then begins to send them 100 messages (with a 1s delay between them). 我已经编写了这个小型服务器,该服务器接受来自客户端的新连接,然后开始向它们发送100条消息(它们之间的延迟为1s)。

The main question is: 主要问题是:

If I'm not spawning new threads, then how the server continues to send delayed messages to several clients, when in fact it's "waiting for connection" ? 如果我没有产生新的线程,那么服务器实际上如何在“等待连接”时如何继续将延迟的消息发送给多个客户端 Are there any hidden low-level signals/events involved, or are there really just new threads? 是否存在任何隐藏的低级信号/事件,或者真的只是新线程?

The second question is: 第二个问题是:

If I'm not using this brand new async Main syntax sugar and I'm not "awaiting" the async task of sending the messages -- am I using the asynchrony correctly? 如果我没有使用这种全新的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();
    }
}

What is the difference between the original code and the version below? 原始代码和下面的版本有什么区别? What difference does async Main make? 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();
    }
}

The answer to the main question: 主要问题的答案:

In the background, as a rule, objects work with some api(winapi for windows). 通常,在后台,对象使用某些api(用于Windows的winapi)工作。 These api can implement asynchrony differently. 这些api可以不同地实现异步。 For example, events(winapi events) or callbacks. 例如,事件(winapi事件)或回调。 So the answer is yes - there are hidden signals or threads. 因此答案是肯定的-存在隐藏的信号或线程。 For example, you can see the Ping class. 例如,您可以看到Ping类。 Ping.InternalSend using ThreadPool.RegisterWaitForSingleObject for the task of asynchrony. 使用ThreadPool.RegisterWaitForSingleObject执行Ping.InternalSend以实现异步任务。

The answer about async Main: 关于异步Main的答案:

In your first code because StartServer is not async the Main method will not get back control until your "accept" cycle ends. 在您的第一个代码中,因为StartServer不异步,所以在您的“接受”周期结束之前, Main方法不会获得控制权。

In your second code, the Main method will get back control then listener.AcceptTcpClientAsync invoked. 在第二个代码中, Main方法将先获得控制权,然后再调用listener.AcceptTcpClientAsync But because you using await StartServer(); 但是因为您使用await StartServer(); the Main method will be wait until StartServer ends. Main方法将等待,直到StartServer结束。

Some code for to explain: 一些代码来解释:

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

This code generate this output: 此代码生成以下输出:

StartServer enter StartServer输入
Main StartServer finished 主启动服务器完成
SendHundredMessages enter SendHundredMessages输入
SendHundredMessages enter SendHundredMessages输入
SendHundredMessages exit SendHundredMessages退出
SendHundredMessages enter SendHundredMessages输入
StartServer exit StartServer退出
task.Wait() finished task.Wait()完成
SendHundredMessages exit SendHundredMessages退出
SendHundredMessages exit SendHundredMessages退出

As you can see, the Main method continued execution right after first Task.Delay . 如您所见, Main方法在第一个Task.Delay之后Task.Delay继续执行。

A warning: 一个警告:

You do not wait end of SendHundredMessages , and this is very bad. 您不必等待SendHundredMessages结束,这非常糟糕。 In example output you can see that " SendHundredMessages ending " after " task.Wait() finished ". 在示例输出中,您可以看到“ task.Wait()完成之后”,“ SendHundredMessages结尾 ”。 In example application of course it not danger, but in real project you can get big problem. 在示例应用程序中当然没有危险,但是在实际项目中您会遇到很大的问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM