Click here to Skip to main content
15,877,675 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
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:

C#
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;
}
Posted

1 solution

When you want multiple tasks to run inline, you need to keep a reference to each task and not await it. Awaiting each task will cause them to run sequentially.

Here is a simple example of running jobs inline.

1. The Job:
C#
class Job
{
    public string Id { get; }
    public int Runtime { get; }

    public Job(string id, int runtime)
    {
        Id = id;
        Runtime = runtime;
    }

    public async Task<Job> ExecuteAsync()
    {
        await Task.Yield();

        int elapsed = 0;
        TimeSpan delay = TimeSpan.FromSeconds(1);

        while (elapsed < Runtime)
        {
            Console.WriteLine($"{Id} ping {elapsed++} of {Runtime}");
            await Task.Delay(delay);
        }

        Console.WriteLine($"{Id} ping {elapsed++} of {Runtime}");
        return this;
    }
}

The manager:
C#
var tasks = new List<Task<Job>>();

var rand = new Random();

for (int i = 0; i < 10; i++)
{
    Job job = new Job($"Task {i}", rand.Next(3, 6));

    tasks.Add(job.ExecuteAsync());
}

while (true)
{
    Task.WaitAny(tasks.ToArray());

    foreach (Task<Job> task in tasks.Where(
        x => x.IsCompleted || x.IsCanceled || x.IsFaulted).ToList())
    {
        var completedJob = await task;
        Console.WriteLine($"!! Job{completedJob.Id} completed");
        tasks.Remove(task);
    }

    if (tasks.Count == 0)
        break;
}

This should give you the concept to help you update your code.
 
Share this answer
 
Comments
Balanka228 22-Feb-24 6:03am    
@Graeme_Grant Thanks, but I don't understand How to assign executor to a job in this concept?
Graeme_Grant 22-Feb-24 6:17am    
The purpose of the solution is to resolve the question: "My problem is the incorrect use of asynchrony. Executors should work in parallel. How to implement it correctly?". It does this clearly in the second part labelled "The manager".

Create a console app, add the code, then run it. Set breakpoints to see how it does it. The key is this line:
Task.WaitAny(tasks.ToArray());

Once you understand how asynchronous code runs, using the sample code provided, then you will be ready to update your code.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900