There is an entity "task". It has the following properties:
- load on the executor - int (from 1 to 5);
- execution time (sec.) - int.
These tasks are received by the distributor (load balancer). There are also executors that receive tasks from the load balancer and execute them. The executors have the following parameters:
- maximum number of simultaneously executed tasks - int;
- maximum total load - int.
The executor executes the task for the time "execution time (sec.)", which is described in the task properties. The load balancer takes tasks from the queue and distribute them among executors. Tasks can be distributed only to those executors, which have the necessary reserves of capacity and reserve of free slots for tasks. The distribution should be as even as possible. It should not be that one performer is fully loaded and the rest are resting.
- should be a visual view in the console showing the workload of each executor (number of tasks performed, how many resources are occupied/free) and the number of tasks in the queue;
- must be implemented asynchrony (all executors must work in parallel);
- at the start of the program a queue of tasks should be created, which will be further read by the balancer and sent to the executors
My problem is the incorrect use of asynchrony. Executors should work in parallel. How to implement it correctly?
What I have tried:
public class TaskEntity
{
public int Load { get; set; }
public int ExecutionTime { get; set; }
public TaskEntity(int load, int executionTime)
{
Load = load;
ExecutionTime = executionTime;
}
}
public class Executor
{
public int MaxTasks { get; set; }
public int MaxLoad { get; set; }
public int CurrentLoad { get; set; }
public List<TaskEntity> CurrentTasks { get; set; }
public Executor(int maxTasks, int maxLoad)
{
MaxTasks = maxTasks;
MaxLoad = maxLoad;
CurrentLoad = 0;
CurrentTasks = new List<TaskEntity>();
}
}
public class LoadBalancer
{
private readonly List<Executor> Executors;
private readonly Queue<TaskEntity> TaskQueue;
public LoadBalancer(List<Executor> executors)
{
Executors = executors;
TaskQueue = new Queue<TaskEntity>();
}
public async Task StartAsync()
{
while (true)
{
if (TaskQueue.Count > 0)
{
var task = TaskQueue.Dequeue();
var availableExecutor = GetAvailableExecutor(task);
if (availableExecutor != null)
{
await ExecuteTaskAsync(task, availableExecutor);
}
}
DisplayLoadStatus();
await Task.Delay(1000);
}
}
public void EnqueueTask(TaskEntity task)
{
TaskQueue.Enqueue(task);
}
private Executor GetAvailableExecutor(TaskEntity task)
{
return Executors.Find(e => e.CurrentLoad + task.Load <= e.MaxLoad &&
e.CurrentTasks.Count < e.MaxTasks);
}
private async Task ExecuteTaskAsync(TaskEntity task, Executor executor)
{
executor.CurrentLoad += task.Load;
executor.CurrentTasks.Add(task);
await Task.Delay(task.ExecutionTime * 1000);
executor.CurrentLoad -= task.Load;
if (executor.CurrentTasks.Contains(task))
{
executor.CurrentTasks.Remove(task);
}
}
private void DisplayLoadStatus()
{
Console.Clear();
foreach (var executor in Executors)
{
Console.WriteLine($"Executor {Executors.IndexOf(executor) + 1}:");
Console.WriteLine($" - Current Workload: {executor.CurrentLoad}/{executor.MaxLoad}");
Console.WriteLine($" - Concurrent Tasks: {executor.CurrentTasks.Count}/{executor.MaxTasks}");
Console.WriteLine();
}
Console.WriteLine($"Task Queue Size: {TaskQueue.Count}");
}
static async Task Main()
{
var executorCount = 3;
var executors = new List<Executor>();
for (int i = 0; i < executorCount; i++)
{
executors.Add(new Executor(maxTasks: 2, maxLoad: 10));
}
var loadBalancer = new LoadBalancer(executors);
var loadBalancerTask = loadBalancer.StartAsync();
var random = new Random();
for (int i = 0; i < 10; i++)
{
var task = new TaskEntity(load: random.Next(1, 6), executionTime: random.Next(1, 6));
loadBalancer.EnqueueTask(task);
await Task.Delay(1000);
}
await loadBalancerTask;
}