Search code examples
c#multithreadingeventsasynchronousautoresetevent

How to manage thread blocking and unblocking with asynchronous events?


Background

I am currently recreating functionality of something with a GUI that works in and, but through a terminal interface. So the error is not on the other end with the events triggering, because it works in original GUI form.

I run a task composed of subtasks on multiple machines.

I subscribe to events which trigger when progress is made and print out a descriptive message. A message is to be printed out for each X subtasks for all Y machines.

Asynchronous multithreading operation then occurs.

I would like to print out a message for each subtask for each machine being resolved only once.

I track completion of the subtasks, and keep a 2D boolean array where rows is machines and columns subtasks.

Problem

When debugging I can see that the event handler methods below are being entered. The print statement in numOfSubtasksFoundEventHandler is run, but before I reach to set the AutoReset event multiple BigTask events are triggered, blocked at the .WaitOne.

However despite the fact that numOfSubtasksFound.Set() is run later, nothing else is printed nor does the program finish execution. Nothing gets past the numOfSubtasksFound.WaitOne s.

If I take out the numOfSubtasksFound.WaitOne in the BigTaskHandler method I receive similar behavior but a few messages stating the BigTask completes and then the program stalls somewhere else.

What is the best way to manage the blocking and unblocking here or is there a small fix?

Goal

What I need is a way to block the operation of the subtask event handler method until numOfSubtasksFoundEventHandler has run once. I only need numOfSubTasksFoundEventHandler to run once only.

Currently the subtask event handler is not being unblocked properly. The switch case code is never executed after numOfSubtasksFound.Set(); is run.

    //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

Below is not necessarily the 1st event to be triggered.

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
}

Subtask events do not necessarily get processed before big task events.

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 progress events occur at the beginning middle and end of processing. I only print out the details of the Cases I want.

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
}

Solution

  • My issue was that after the 1st event was triggered, other subtask event handler event would call .WaitOne which would block. This could happen after the number of subtasks is discovered. The issue then was the .Set would only be called once, and it would never be unblocked.

    So using a boolean flag to be set when the number of subtasks is discovered, and locking the sub task event handler was the way to go.