method - task c# ejemplo
Enfoque correcto para TcpListener asincrónico usando async/await (2)
He estado pensando en cuál es la forma correcta de configurar un servidor TCP mediante el uso de programación asincrónica.
Por lo general, genero un hilo por solicitud entrante, pero me gustaría aprovechar al máximo el ThreadPool, por lo que cuando las conexiones están inactivas, no hay hilos bloqueados.
Primero crearía el oyente y comenzaría a aceptar clientes, en este caso, en una aplicación de consola:
static void Main(string[] args)
{
CancellationTokenSource cancellation = new CancellationTokenSource();
var endpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8001);
TcpListener server = new TcpListener(endpoint);
server.Start();
var task = AcceptTcpClients(server, cancellation.Token);
Console.ReadKey(true);
cancellation.Cancel();
await task;
Console.ReadKey(true);
}
En ese método, me gustaría aceptar solicitudes entrantes y generar una nueva tarea para manejar la conexión, por lo que el ciclo puede volver a aceptar más clientes:
static async Task AcceptTcpClients(TcpListener server, CancellationToken token)
{
while (!token.IsCancellationRequested)
{
var ws = await server.AcceptTcpClientAsync();
Task.Factory.StartNew(async () =>
{
while (ws.IsConnected && !token.IsCancellationRequested)
{
String msg = await ws.ReadAsync();
if (msg != null)
await ws.WriteAsync(ProcessResponse(msg));
}
}, token);
}
}
Crear una tarea nueva no significa necesariamente un nuevo hilo, pero ¿es esta la manera correcta? ¿Estoy aprovechando el ThreadPool o hay algo más que pueda hacer?
¿Hay alguna trampa potencial en este enfoque?
La await task;
en tu Main
no compilará; deberás usar task.Wait();
si quieres bloquearlo
Además, debe usar Task.Run
lugar de Task.Factory.StartNew
en la programación asincrónica.
Crear una tarea nueva no significa necesariamente un nuevo hilo, pero ¿es esta la manera correcta?
Sin duda puede iniciar tareas por separado (utilizando Task.Run
). Aunque no es necesario. También podría llamar fácilmente a un método async
separado para manejar las conexiones de socket individuales.
Sin embargo, hay algunos problemas con su manejo real de socket. La propiedad Connected
es prácticamente inútil. Siempre debe leer continuamente desde un socket conectado, incluso mientras escribe en él. Además, debe escribir mensajes "keepalive" o tener un tiempo de espera en sus lecturas, para que pueda detectar situaciones medio abiertas. Mantengo una FAQ de TCP / IP .NET que explica estos problemas comunes.
Realmente, recomiendo enfáticamente que las personas no escriban servidores o clientes TCP / IP. Hay toneladas de trampas. Sería mucho mejor autoasignar WebAPI y / o SignalR, si es posible.
Para evitar que un servidor acepte el bucle correctamente, registro una devolución de llamada que deja de escuchar cuando se cancela la cancellationToken.Register(listener.Stop);
).
Esto arrojará una ObjectDisposedException en el await listener.AcceptTcpClientAsync();
pendiente. AceptTcpClientAsync await listener.AcceptTcpClientAsync();
eso es fácil de capturar
No es necesario Task.Run (HandleClient ()), porque llamar a un método async devuelve una tarea que se ejecuta en paralelo.
public async Task Run(CancellationToken cancellationToken)
{
TcpListener listener = new TcpListener(address, port);
listener.Start();
cancellationToken.Register(listener.Stop);
while (!cancellationToken.IsCancellationRequested)
{
try
{
TcpClient client = await listener.AcceptTcpClientAsync();
var clientTask = protocol.HandleClient(client, cancellationToken)
.ContinueWith((antecedent) => client.Dispose())
.ContinueWith((antecedent)=> logger.LogInformation("Client disposed."));
}
catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested)
{
logger.LogInformation("TcpListener stopped listening because cancellation was requested.");
}
catch (Exception ex)
{
logger.LogError(new EventId(), ex, $"Error handling client: {ex.Message}");
}
}
}