如何使用异步事件管理线程阻塞和取消阻塞

本文关键字:取消 线程 管理 何使用 异步 事件 | 更新日期: 2023-09-27 18:00:12

背景

我目前正在用一个在和中工作的GUI重新创建一些功能,但通过一个终端界面。因此,错误不在事件触发的另一端,因为它以原始GUI形式工作

我在多台机器上运行由子任务组成的任务。

我订阅了在取得进展时触发的事件,并打印出一条描述性消息。将为所有Y台机器的每个X个子任务打印一条消息。

然后发生异步多线程操作。

我想为每台只解决一次的机器的每个子任务打印一条消息。

我跟踪子任务的完成情况,并保留一个2D布尔数组,其中行是机器,列是子任务。

问题

调试时,我可以看到正在输入下面的事件处理程序方法。numOfSubtasksFoundEventHandler中的print语句正在运行,但在我设置AutoReset事件之前,触发了多个BigTask事件,并在.WaitOne.处被阻止

然而,尽管稍后会运行numOfSubtasksFound.Set(),但不会打印其他内容,程序也不会完成执行。没有任何东西超过numOfSubtasksFound.WaitOnes.

如果我去掉BigTaskHandler方法中的numOfSubtasksFound.WaitOne,我会收到类似的行为,但会收到一些消息,说明BigTask完成,然后程序在其他地方暂停。

管理这里的阻塞和解除阻塞的最佳方法是什么?或者有小的解决方案吗?

目标

我需要的是一种方法来阻止子任务事件处理程序方法的操作,直到numOfSubtasksFoundEventHandler运行一次为止。我只需要numOfSubTasksFoundEventHandler就可以只运行一次

当前,子任务事件处理程序未正确解除阻止。开关用例代码在numOfSubtasksFound.Set()之后永远不会执行;正在运行。

    //MAIN
    bool[] machinesDoneTasks = new bool[numOfMachines];
    bool[][] machinesDoneSubtasks = new bool[numOfMachines][];
    try
    {
        //thread/task blocking
        numOfSubtasksFound = new AutoResetEvent(false);
        AllSubTasksDone = new AutoResetEvent(false);
        AllBigTasksDone = new AutoResetEvent(false);
        //Subscribe to events to get number of subtasks and print useful information as tasks progress
        numOfSubtasksFoundEvent += numOfSubtasksFoundEventHandler;
        SubTaskProgEvent += SubTaskEventProgHandler; //prog stands for progress
        BigTaskProgEvent += BigTaskProgEventHandler;
        RunAllTasksOnAllMachines();//this will trigger the events above
        //Don't exit program until those descriptive messages have been printed
        numOfSubtasksFound.WaitOne();
        AllSubTasksDone.WaitOne();
        //SubTaskProgEvent -= SubTaskProgEventHandler;
        AllBigTasksDone.WaitOne();
        //BigTaskProgEvent -= BigTaskProgEventHandler;
    }
    catch (Exception e)
    {
        //print exceptions
    }
    //END MAIN

以下不一定是要触发的第一个事件。

internal void numOfSubtasksFoundEventHandler(object sender, EventArgs e)
{
    //get number of subtasks from args after checking for nulls, empty arrays
    for (int i = 0; i < numOfSubtasks; i++)
        machinesDoneSubtasks[i] = new bool[numOfSubtasks];
    Console.WriteLine("number of subtasks found");
    numOfSubtasksFoundEvent -= numOfSubtasksFoundEventHandler;//don't subscribe to event where we get this from anymore
    if (numOfSubtasksFound != null)
        numOfSubtasksFound.Set(); //stop blocking
}

子任务事件不一定要在大任务事件之前处理。

internal void SubtaskEventProgHandler(object sender, EventArgs e)
{
    //null, empty checks on args
    //Wait until we know how many subtasks there are and the 2D boolean array is fully built
    numOfSubtasksFound.WaitOne();
    switch (e.WhatHappened)
    {
        Case.TaskComplete:
            Console.Write(e.Machine + " is done subtask " + e.subTask);
            //logic to determine machine and subtask
            machinesDoneSubtasks[machine][Subtask] = true;
            if (AllSubTasksDone != null && machinesDoneSubtasks.OfType<bool>().All(x => x))
                AllSubTasksDone.Set(); //stop blocking when 2D array is all true
            break;
            //other cases, different prints, but same idea
    }    
}

BigTask进度事件发生在处理的开始、中间和结束。我只打印出我想要的案例的详细信息。

internal void BigTaskProgEventHandler(object sender, EventArgs e)
{
    //Wait until we know how many subtasks there are and the 2D boolean array is fully built before printing
    numOfSubtasksFound.WaitOne();
    //null, empty exception checks
    switch (e.WhatHappened)
    {
           Case.TaskComplete:
           Console.Write(e.Machine + " is done task " + e.subTask);
    //logic to determine machine
    machinesDoneTasks[machine] = true;
    if (AllBigTasksDone != null && machinesDoneTasks.All(x => x))
        AllBigTasksDone.Set();
    break;
    }
    //other cases, different prints, but same idea
}

如何使用异步事件管理线程阻塞和取消阻塞

异步/等待模型的示例。在每台机器上运行许多任务并计算一个值。完成所有任务后,值将显示在控制台上。

  static void Main(string[] args)
        {
            var service = new DispatchTasksOnMachinesService(8, 3);
            service.DispatchTasks();
            Console.Read();
        }
        class DispatchTasksOnMachinesService
        {
            int numOfMachines;
            int tasksPerMachine;
            [ThreadStatic]
            private Random random = new Random();
            public DispatchTasksOnMachinesService(int numOfMachines, int tasksPerMachine)
            {
                this.numOfMachines = numOfMachines;
                this.tasksPerMachine = tasksPerMachine;
            }
            public async void DispatchTasks()
            {
                var tasks = new List<Task<Tuple<Guid, Machine, int>>>();
                for (int i = 0; i < this.numOfMachines; i++)
                {
                    var j = i;
                    for (int k = 0; k < this.tasksPerMachine; k++)
                    {
                        var task = Task.Run(() => Foo(Guid.NewGuid(), new Machine("machine" + j)));
                        tasks.Add(task);
                    }
                }
                var results = await Task.WhenAll<Tuple<Guid, Machine, int>>(tasks);
                foreach (var result in results)
                {
                    Console.WriteLine($"Task {result.Item1} on  {result.Item2} yielded result {result.Item3}");
                }
            }
            private Tuple<Guid, Machine, int> Foo(Guid taskId, Machine machine)
            {
                Thread.Sleep(TimeSpan.FromSeconds(random.Next(1,5)));
                Console.WriteLine($"Task {taskId} has completed on {machine}");
                return new Tuple<Guid, Machine, int>(taskId, machine, random.Next(500, 2000));
            }
        }
        class Machine
        {
            public string Name { get; private set; }
            public Machine(string name)
            {
                this.Name = name;
            }
            public override string ToString()
            {
                return this.Name;
            }
        }

我的问题是,在第一个事件被触发后,其他子任务事件处理程序事件将调用.WaitOne,这将被阻止。这可能发生在发现子任务数之后。当时的问题是。Set只会被调用一次,而且永远不会被取消阻止。

因此,当发现子任务的数量时,可以使用布尔标志来设置,并锁定子任务事件处理程序。