【解決方法】このコードで非同期を正しく実装するにはどうすればよいですか?


「タスク」という実体があります。 次のような特性があります。
– エグゼキューターのロード – int (1 から 5);
– 実行時間 (秒) – int.
これらのタスクはディストリビュータ(ロードバランサ)によって受信されます。 ロードバランサーからタスクを受け取って実行するエグゼキュータもあります。 エグゼキュータには次のパラメータがあります。
– 同時に実行されるタスクの最大数 – int;
– 最大総負荷 – 整数

実行者は、タスクのプロパティに記載されている「実行時間(秒)」だけタスクを実行します。 ロード バランサーはキューからタスクを取得し、実行プログラム間でタスクを分散します。 タスクは、タスクに必要な容量の予約と空きスロットの予約を持つ実行者にのみ配布できます。 分布は可能な限り均一である必要があります。 一人の出演者が全力で仕事をしていて、残りの出演者が休んでいるということがあってはならないのです。

– 各エグゼキュータのワークロード (実行されたタスクの数、占有されている/空いているリソースの数) とキュー内のタスクの数を示すコンソール内の視覚的なビューである必要があります。
– 非同期で実装する必要があります (すべての実行プログラムが並行して動作する必要があります)。
– プログラムの開始時にタスクのキューを作成する必要があります。タスクのキューはバランサーによってさらに読み取られ、実行プログラムに送信されます。

私の問題は、非同期の誤った使用です。 エグゼキュータは並行して作業する必要があります。 正しく実装するにはどうすればよいでしょうか?

私が試したこと:

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;
}

解決策 1

複数のタスクをインラインで実行する場合は、各タスクを待たずに、各タスクへの参照を保持する必要があります。 各タスクを待機すると、タスクが順番に実行されます。

以下に、ジョブをインラインで実行する簡単な例を示します。

1. 仕事:

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;
    }
}

マネージャー:

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;
}

これにより、コードを更新するのに役立つ概念が得られます。

コメント

タイトルとURLをコピーしました