我想在.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();
}
}
主要问题的答案:
在后台,通常,对象使用一些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结束”。在示例应用当然不是危险,但在实际项目中你可以得到大问题。