Tengo un caso de uso en el que mi servicio se comunica con un servidor que tiene varios servidores alternativos. Estoy tratando de desarrollar un mecanismo en el que primero hablo con el servidor principal, pero no responde en x segundos, pruebo con otro servidor alternativo y así sucesivamente hasta que alguno de ellos responda.
Con mi investigación, descubrí que posiblemente podría usar Timer() o LinkedCancellationToken pero ninguno de ellos es adecuado para mi caso de uso.
Timer() programaría algo para más tarde, pero quiero programarlo solo si el servidor principal no responde dentro del límite.
LinkedCancellationToken me permitiría cancelar la llamada si se cancela alguno de los tokens de cancelación vinculados, pero ese no es mi caso de uso.
¿Alguna idea sobre cómo podría implementar esto?
Este es un uso bastante sencillo de promesas + un bucle.
public async Task<Connection> ConnectAsync() { foreach (var srv in Servers) { var cts = new CancellationTokenSource(ConnectionTimeout); try { return await srv.ConnectAsync(cts.Token); } catch (OperationCancelledException) { // no-op } } throw new NoServerRespondedException(); }Comience cada intento, si se agota el tiempo, pasa al siguiente. Si todo el tiempo de espera, falla.
Si no desea cancelar las solicitudes anteriores, entonces Task.WhenAny es su amigo.
public async Task<Connection> ConnectAsync() { var cts = new CancellationTokenSource(); var inProgress = new List<Task<Connection>>(); foreach (var srv in Servers) { // Ensure task does not complete before success inProgress.Add(async () => { try { return await srv.ConnectAsync(cts.Token)).ConfigureAsync(false); } catch (OperationCancelledException) { /* no-op */} catch (Exception ex) { // TODO: Log connection failure // prevent this task from completing before cancellation var cancellationTask = new TaskCompletionSource<bool>(); cts.Token.Register(() => cancellationTask.TrySetResult(true)); await cancellationTask.Task; } }); var completedTask = Task.WhenAny(inProgress.Concat(new []{ Task.Delay(ConnectionInterval) }); if (completedTask is Task<Connection> connectedTask) { cts.Cancel(); return await connectedTask; } } cts.Cancel(); throw new NoServerRespondedException(); }Lo anterior también volverá en caso de falla de cualquier tarea. Si desea ignorar las fallas de
En principio, esto es bastante simple: puede await Task.WhenAny(...) , pasando las tareas que ha creado más otra tarea de "retraso" que finalizará cuando esté cansado de esperar y listo para probar otro servidor. .
Por supuesto, las mejores prácticas dicen que nunca debe dejar ninguna tarea sin esperar, y debe cancelar las otras solicitudes una vez que haya recibido una buena solicitud. Eso puede hacer las cosas un poco más complicadas:
async Task<Response> GetFirstServerResponse(IEnumerable<string> serverNames, CancellationToken cancellationToken) { var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); var tasks = new List<Task<Response>>(); try { foreach (var serverName in serverNames) { tasks.Add(TalkToServer(serverName, linkedTokenSource.Token)); await Task.WhenAny(tasks.Append(Task.Delay(delay))); var completedTask = tasks.FirstOrDefault(t => t.IsCompleted); if(completedTask != null) { return await completedTask; } } var firstCompleted = await Task.WhenAny(tasks); return await firstCompleted; } finally { // Make all the incomplete tasks stop trying. linkedTokenSource.Cancel(); try { // Never leave tasks unawaited. // Since we cancelled these, they should all finish very quickly now. await Task.WhenAll(tasks); } catch (OperationCanceledException) // ignore tasks that got cancelled. { } } }Juega con él en este dotnetfiddle .
Dependiendo de sus requisitos, es posible que deba modificar esto. Por ejemplo, asumo que si una solicitud finaliza con un error, entonces desea rescatar y sacar a la luz ese error. Si espera que se produzcan ciertos tipos de errores intermitentes en un servidor pero no en otros, y desea ignorar los errores siempre que la solicitud se realice correctamente, deberá realizar algunos cambios en este código.