'Proper way to have a list of sync or async methods called in a safe manner

I have a use case where I made an EventManager class that essentially have two methods. One takes a method pointer in the form of an Action<eventType> to register that method pointer as one of many handlers for eventType. I pass both "normal" methods and async methods to this. The other takes an instance of an eventType as a signal to raise that event and call all the previously registered handlers.

When an event is raised, I call all the handlers based on their methodInfo and invoke them one by one. I do not await any of them, and treat all the same, even though some are sync and some are async, and that all works well... or so it seemed for a long time.

While I have extensively used the async pattern, there are apparently still details which can kick me in the head at times, and things I am simply unaware of, and I have for a while had issues with random uncaught exceptions, which I am pretty sure comes from this implementation.

After seeking some more advice on good and bad async here, I have redesigned so I can pass either an Action<eventType> or a Func<Task, eventType>, and store it as methodInfo so I can later properly invoke and await the call when it is an async method.

I made a proof of concept, which seems to work correctly, before making sweeping changes in the live project, and would like if someone with more async insight than myself could comment on this. Is this in fact proper and safe now, or am I just headed into a whole other set of problems?

It is a very simplified test case demonstrating the core of my new system. Somewhere you call the eventManager to register a method to handle some event and elsewhere someone else calls the eventManager to raise the event, and the manager calls all relevant handlers while trying to await the async ones. All is running main thread.

In this example, all of this is represented by the Test() method.

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;

namespace test
{
    public static class ext
    {
        public static async Task InvokeAsync(this MethodInfo @this, object obj, params object[] parameters)
        {
            dynamic awaitable = @this.Invoke(obj, parameters);
            if (awaitable!=null) await awaitable;            
        }
    }

internal class Program
{
    static void Main(string[] args)
    {
        Test(); //what is the proper way to start´this async chain from a non async method?
        Console.ReadKey();
    }

    //a description of an event handler containing the method and object to handle it
    private struct RegistredEventHandler
    {
        public object HandlerObject;
        public MethodInfo HandlerMethod;
    }

    //list of all registered handlers
    private static List<RegistredEventHandler> handlerList = new List<RegistredEventHandler>();

    //first async method in the chain doing both the registering and the raising of events as a simple test
    private static async Task Test()
    {
        //register an event handler for both the async and the serial method

        Func<int, Task> asyncFunc = TestAsync;
        var hAsync = new RegistredEventHandler()
        {
            HandlerObject = asyncFunc.Target,
            HandlerMethod = asyncFunc.Method
        };
        handlerList.Add(hAsync);

        Action<int> serialAction = TestSerial;
        var hSerial = new RegistredEventHandler()
        {
            HandlerObject = serialAction.Target,
            HandlerMethod = serialAction.Method
        };
        handlerList.Add(hSerial);

        //try invoking and awating both handlers
        foreach (var eventHandler in handlerList)
        {
            await eventHandler.HandlerMethod.InvokeAsync(eventHandler.HandlerObject, new object[] { 10});
        }
    }
    static void TestSerial(int i)
    {
        Console.WriteLine($"Serial argument {i}");
    }

    static async Task TestAsync(int i)
    {
        await Task.Delay(5000);
        Console.WriteLine($"Async argument {i}");
    }
}


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source