From d7d401f5dd44dc38a8b17ae8688ea433d46b413d Mon Sep 17 00:00:00 2001 From: Roman Date: Sun, 22 Feb 2026 11:15:02 +0500 Subject: [PATCH 1/6] =?UTF-8?q?Parallel=20(1=20=D0=B1=D0=B0=D0=BB=D0=BB)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ждем ответа хотя бы от одной реплики и сразу же возвращаем ответ --- .../Clients/ParallelClusterClient.cs | 38 +++++++++++++------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/homework 2/ClusterClient/Clients/ParallelClusterClient.cs b/homework 2/ClusterClient/Clients/ParallelClusterClient.cs index 5531800..220171d 100644 --- a/homework 2/ClusterClient/Clients/ParallelClusterClient.cs +++ b/homework 2/ClusterClient/Clients/ParallelClusterClient.cs @@ -1,23 +1,37 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; using log4net; -namespace ClusterClient.Clients +namespace ClusterClient.Clients; + +public class ParallelClusterClient(string[] replicaAddresses) : ClusterClientBase(replicaAddresses) { - public class ParallelClusterClient : ClusterClientBase + public override async Task ProcessRequestAsync(string query, TimeSpan timeout) { - public ParallelClusterClient(string[] replicaAddresses) : base(replicaAddresses) - { - } + if (ReplicaAddresses == null || ReplicaAddresses.Length == 0) + throw new InvalidOperationException("Реплика адресов не указана"); - public override Task ProcessRequestAsync(string query, TimeSpan timeout) + var tasks = new Task[ReplicaAddresses.Length]; + + for (var i = 0; i < ReplicaAddresses.Length; i++) { - throw new NotImplementedException(); + var uri = ReplicaAddresses[i]; + var webRequest = CreateRequest(uri + "?query=" + query); + + Log.InfoFormat($"Обработка {webRequest.RequestUri}"); + + tasks[i] = ProcessRequestAsync(webRequest); } - protected override ILog Log => LogManager.GetLogger(typeof(ParallelClusterClient)); + var anyReplicaTask = Task.WhenAny(tasks); + var timeoutTask = Task.Delay(timeout); + + var completed = await Task.WhenAny(anyReplicaTask, timeoutTask); + if (ReferenceEquals(completed, timeoutTask)) + throw new TimeoutException(); + + return await anyReplicaTask.Result; } -} + + protected override ILog Log => LogManager.GetLogger(typeof(ParallelClusterClient)); +} \ No newline at end of file From 868529cdbc4d1d2a2e81b5c79d2c156f3c004f4e Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 23 Feb 2026 19:02:48 +0500 Subject: [PATCH 2/6] =?UTF-8?q?RoundRobin=20(2=20=D0=B1=D0=B0=D0=BB=D0=BB?= =?UTF-8?q?=D0=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Обработка реплик, с одинаковым таймаутом. Если не успела обработаться, обрабатываем следующую --- .../Clients/RoundRobinClusterClient.cs | 52 ++++++++++++++----- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/homework 2/ClusterClient/Clients/RoundRobinClusterClient.cs b/homework 2/ClusterClient/Clients/RoundRobinClusterClient.cs index 0293628..8880e2e 100644 --- a/homework 2/ClusterClient/Clients/RoundRobinClusterClient.cs +++ b/homework 2/ClusterClient/Clients/RoundRobinClusterClient.cs @@ -1,23 +1,51 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; using log4net; -namespace ClusterClient.Clients +namespace ClusterClient.Clients; + +public class RoundRobinClusterClient(string[] replicaAddresses) : ClusterClientBase(replicaAddresses) { - public class RoundRobinClusterClient : ClusterClientBase + public override async Task ProcessRequestAsync(string query, TimeSpan timeout) { - public RoundRobinClusterClient(string[] replicaAddresses) : base(replicaAddresses) - { - } + if (ReplicaAddresses == null || ReplicaAddresses.Length == 0) + throw new InvalidOperationException("Реплика адресов не указана"); - public override Task ProcessRequestAsync(string query, TimeSpan timeout) + var perReplicaTimeout = TimeSpan.FromTicks(timeout.Ticks / ReplicaAddresses.Length); + + Exception lastError = null; + + foreach (var uri in ReplicaAddresses) { - throw new NotImplementedException(); + var webRequest = CreateRequest(uri + "?query=" + query); + + Log.InfoFormat($"Обработка {webRequest.RequestUri}"); + + var resultTask = ProcessRequestAsync(webRequest); + + await Task.WhenAny(resultTask, Task.Delay(perReplicaTimeout)); + + if (!resultTask.IsCompleted) + { + _ = resultTask.ContinueWith(t => _ = t.Exception, TaskContinuationOptions.OnlyOnFaulted); + continue; + } + + try + { + return await resultTask; + } + catch (Exception e) + { + lastError = e; + } } - protected override ILog Log => LogManager.GetLogger(typeof(RoundRobinClusterClient)); + if (lastError != null) + throw lastError; + + throw new TimeoutException(); } -} + + protected override ILog Log => LogManager.GetLogger(typeof(RoundRobinClusterClient)); +} \ No newline at end of file From fc7b67216c5de0e6334e39911629cef07445fb42 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 25 Feb 2026 20:36:28 +0500 Subject: [PATCH 3/6] =?UTF-8?q?Smart=20(2=20=D0=B1=D0=B0=D0=BB=D0=BB=D0=B0?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ждем ответа от реплик при запросе последующих --- .../Clients/SmartClusterClient.cs | 59 +++++++++++++++---- 1 file changed, 47 insertions(+), 12 deletions(-) diff --git a/homework 2/ClusterClient/Clients/SmartClusterClient.cs b/homework 2/ClusterClient/Clients/SmartClusterClient.cs index eb06d8b..614a4bf 100644 --- a/homework 2/ClusterClient/Clients/SmartClusterClient.cs +++ b/homework 2/ClusterClient/Clients/SmartClusterClient.cs @@ -1,23 +1,58 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Threading; using System.Threading.Tasks; using log4net; -namespace ClusterClient.Clients +namespace ClusterClient.Clients; + +public class SmartClusterClient(string[] replicaAddresses) : ClusterClientBase(replicaAddresses) { - public class SmartClusterClient : ClusterClientBase + public override async Task ProcessRequestAsync(string query, TimeSpan timeout) { - public SmartClusterClient(string[] replicaAddresses) : base(replicaAddresses) - { - } + if (ReplicaAddresses == null || ReplicaAddresses.Length == 0) + throw new InvalidOperationException("Реплика адресов не указана"); + + var perReplicaTimeout = TimeSpan.FromTicks(timeout.Ticks / ReplicaAddresses.Length); + var firstSuccess = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - public override Task ProcessRequestAsync(string query, TimeSpan timeout) + var remaining = ReplicaAddresses.Length; + Exception lastError = null; + + foreach (var uri in ReplicaAddresses) { - throw new NotImplementedException(); + var webRequest = CreateRequest(uri + "?query=" + query); + + Log.InfoFormat($"Обработка {webRequest.RequestUri}"); + + var task = ProcessRequestAsync(webRequest); + + _ = task.ContinueWith(t => + { + if (t.Status == TaskStatus.RanToCompletion) + { + firstSuccess.TrySetResult(t.Result); + return; + } + + var ex = t.Exception?.GetBaseException() ?? new TaskCanceledException(t); + Interlocked.Exchange(ref lastError, ex); + + if (Interlocked.Decrement(ref remaining) == 0) + firstSuccess.TrySetException(Volatile.Read(ref lastError) ?? + new Exception( + "Не удалось получить успешный ответ ни от одной реплики")); + }, TaskContinuationOptions.ExecuteSynchronously); + + var completed = await Task.WhenAny(firstSuccess.Task, Task.Delay(perReplicaTimeout)); + if (completed == firstSuccess.Task) + return await firstSuccess.Task; } - protected override ILog Log => LogManager.GetLogger(typeof(SmartClusterClient)); + if (firstSuccess.Task.IsCompleted) + return await firstSuccess.Task; + + throw new TimeoutException(); } -} + + protected override ILog Log => LogManager.GetLogger(typeof(SmartClusterClient)); +} \ No newline at end of file From c3a8b369ec041f7d8d329ae415bf159a26d12222 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 5 Mar 2026 12:26:23 +0500 Subject: [PATCH 4/6] =?UTF-8?q?=D0=9F=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20Para?= =?UTF-8?q?llelClusterClient?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Clients/ParallelClusterClient.cs | 38 +++++++++++++------ 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/homework 2/ClusterClient/Clients/ParallelClusterClient.cs b/homework 2/ClusterClient/Clients/ParallelClusterClient.cs index 220171d..037396b 100644 --- a/homework 2/ClusterClient/Clients/ParallelClusterClient.cs +++ b/homework 2/ClusterClient/Clients/ParallelClusterClient.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; using log4net; @@ -11,26 +12,41 @@ public override async Task ProcessRequestAsync(string query, TimeSpan ti if (ReplicaAddresses == null || ReplicaAddresses.Length == 0) throw new InvalidOperationException("Реплика адресов не указана"); - var tasks = new Task[ReplicaAddresses.Length]; + var firstSuccess = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - for (var i = 0; i < ReplicaAddresses.Length; i++) + var remaining = ReplicaAddresses.Length; + Exception? lastError = null; + + foreach (var uri in ReplicaAddresses) { - var uri = ReplicaAddresses[i]; var webRequest = CreateRequest(uri + "?query=" + query); - Log.InfoFormat($"Обработка {webRequest.RequestUri}"); - tasks[i] = ProcessRequestAsync(webRequest); - } + var task = ProcessRequestAsync(webRequest); - var anyReplicaTask = Task.WhenAny(tasks); - var timeoutTask = Task.Delay(timeout); + _ = task.ContinueWith(t => + { + if (t.Status == TaskStatus.RanToCompletion) + { + firstSuccess.TrySetResult(t.Result); + return; + } + + var ex = t.Exception?.GetBaseException() ?? new TaskCanceledException(t); + lastError = ex; + _ = t.Exception; + + if (Interlocked.Decrement(ref remaining) == 0) + firstSuccess.TrySetException(lastError ?? + new Exception("Не удалось получить ответ ни от одной реплики")); + }, TaskContinuationOptions.ExecuteSynchronously); + } - var completed = await Task.WhenAny(anyReplicaTask, timeoutTask); - if (ReferenceEquals(completed, timeoutTask)) + var completed = await Task.WhenAny(firstSuccess.Task, Task.Delay(timeout)); + if (completed != firstSuccess.Task) throw new TimeoutException(); - return await anyReplicaTask.Result; + return await firstSuccess.Task; } protected override ILog Log => LogManager.GetLogger(typeof(ParallelClusterClient)); From 3d07edc30202dab970ef960bbefe99d99ab79d6d Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 5 Mar 2026 13:57:29 +0500 Subject: [PATCH 5/6] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BF=D1=80=D0=BE=D0=B1=D0=BB=D0=B5=D0=BC=D1=83=20?= =?UTF-8?q?=D0=B8=D0=B7-=D0=B7=D0=B0=20=D0=BA=D0=BE=D1=82=D0=BE=D1=80?= =?UTF-8?q?=D0=BE=D0=B9=20=D0=BD=D0=B5=20=D0=BF=D1=80=D0=BE=D1=85=D0=BE?= =?UTF-8?q?=D0=B4=D0=B8=D0=BB=20=D1=82=D0=B5=D1=81=D1=82=20ShouldNotSpendT?= =?UTF-8?q?imeOnBad?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Clients/RoundRobinClusterClient.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/homework 2/ClusterClient/Clients/RoundRobinClusterClient.cs b/homework 2/ClusterClient/Clients/RoundRobinClusterClient.cs index 8880e2e..fbfdd50 100644 --- a/homework 2/ClusterClient/Clients/RoundRobinClusterClient.cs +++ b/homework 2/ClusterClient/Clients/RoundRobinClusterClient.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Threading.Tasks; using log4net; @@ -11,21 +12,27 @@ public override async Task ProcessRequestAsync(string query, TimeSpan ti if (ReplicaAddresses == null || ReplicaAddresses.Length == 0) throw new InvalidOperationException("Реплика адресов не указана"); - var perReplicaTimeout = TimeSpan.FromTicks(timeout.Ticks / ReplicaAddresses.Length); - + var sw = Stopwatch.StartNew(); Exception lastError = null; - foreach (var uri in ReplicaAddresses) + for (var i = 0; i < ReplicaAddresses.Length; i++) { + var remaining = timeout - sw.Elapsed; + if (remaining <= TimeSpan.Zero) + break; + + var remainingReplicas = ReplicaAddresses.Length - i; + var slice = TimeSpan.FromTicks(remaining.Ticks / remainingReplicas); + + var uri = ReplicaAddresses[i]; var webRequest = CreateRequest(uri + "?query=" + query); Log.InfoFormat($"Обработка {webRequest.RequestUri}"); var resultTask = ProcessRequestAsync(webRequest); - - await Task.WhenAny(resultTask, Task.Delay(perReplicaTimeout)); - if (!resultTask.IsCompleted) + var completed = await Task.WhenAny(resultTask, Task.Delay(slice)); + if (completed != resultTask) { _ = resultTask.ContinueWith(t => _ = t.Exception, TaskContinuationOptions.OnlyOnFaulted); continue; From 48129e4141c5f959515a99fea4f712ade4bc5ff3 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 5 Mar 2026 15:35:04 +0500 Subject: [PATCH 6/6] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20SmartClusterClient?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Clients/SmartClusterClient.cs | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/homework 2/ClusterClient/Clients/SmartClusterClient.cs b/homework 2/ClusterClient/Clients/SmartClusterClient.cs index 614a4bf..6ad225b 100644 --- a/homework 2/ClusterClient/Clients/SmartClusterClient.cs +++ b/homework 2/ClusterClient/Clients/SmartClusterClient.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using log4net; @@ -12,6 +13,8 @@ public override async Task ProcessRequestAsync(string query, TimeSpan ti if (ReplicaAddresses == null || ReplicaAddresses.Length == 0) throw new InvalidOperationException("Реплика адресов не указана"); + var sw = Stopwatch.StartNew(); + var perReplicaTimeout = TimeSpan.FromTicks(timeout.Ticks / ReplicaAddresses.Length); var firstSuccess = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -21,7 +24,6 @@ public override async Task ProcessRequestAsync(string query, TimeSpan ti foreach (var uri in ReplicaAddresses) { var webRequest = CreateRequest(uri + "?query=" + query); - Log.InfoFormat($"Обработка {webRequest.RequestUri}"); var task = ProcessRequestAsync(webRequest); @@ -38,12 +40,26 @@ public override async Task ProcessRequestAsync(string query, TimeSpan ti Interlocked.Exchange(ref lastError, ex); if (Interlocked.Decrement(ref remaining) == 0) - firstSuccess.TrySetException(Volatile.Read(ref lastError) ?? - new Exception( - "Не удалось получить успешный ответ ни от одной реплики")); + firstSuccess.TrySetException( + Volatile.Read(ref lastError) ?? + new Exception("Не удалось получить успешный ответ ни от одной реплики")); }, TaskContinuationOptions.ExecuteSynchronously); - var completed = await Task.WhenAny(firstSuccess.Task, Task.Delay(perReplicaTimeout)); + var completed = await Task.WhenAny(firstSuccess.Task, task, Task.Delay(perReplicaTimeout)); + + if (completed == firstSuccess.Task) + return await firstSuccess.Task; + + if (completed != task) continue; + + if (task.Status == TaskStatus.RanToCompletion) + return task.Result; + } + + var remainingTotal = timeout - sw.Elapsed; + if (remainingTotal > TimeSpan.Zero) + { + var completed = await Task.WhenAny(firstSuccess.Task, Task.Delay(remainingTotal)); if (completed == firstSuccess.Task) return await firstSuccess.Task; }