Sign Up

Sign Up to our social questions and Answers Engine to ask questions, answer people’s questions, and connect with other people.

Have an account? Sign In

Have an account? Sign In Now

Sign In

Login to our social questions & Answers Engine to ask questions answer people’s questions & connect with other people.

Sign Up Here

Forgot Password?

Don't have account, Sign Up Here

Forgot Password

Lost your password? Please enter your email address. You will receive a link and will create a new password via email.

Have an account? Sign In Now

You must login to ask a question.

Forgot Password?

Need An Account, Sign Up Here

Please briefly explain why you feel this question should be reported.

Please briefly explain why you feel this answer should be reported.

Please briefly explain why you feel this user should be reported.

Sign InSign Up

The Archive Base

The Archive Base Logo The Archive Base Logo

The Archive Base Navigation

  • SEARCH
  • Home
  • About Us
  • Blog
  • Contact Us
Search
Ask A Question

Mobile menu

Close
Ask a Question
  • Home
  • Add group
  • Groups page
  • Feed
  • User Profile
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Buy Points
  • Users
  • Help
  • Buy Theme
  • SEARCH
Home/ Questions/Q 8681767
In Process

The Archive Base Latest Questions

Editorial Team
  • 0
Editorial Team
Asked: June 12, 20262026-06-12T21:35:45+00:00 2026-06-12T21:35:45+00:00

Using the new async/await model it’s fairly straightforward to generate a Task that is

  • 0

Using the new async/await model it’s fairly straightforward to generate a Task that is completed when an event fires; you just need to follow this pattern:

public class MyClass
{
    public event Action OnCompletion;
}

public static Task FromEvent(MyClass obj)
{
    TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();

    obj.OnCompletion += () =>
        {
            tcs.SetResult(null);
        };

    return tcs.Task;
}

This then allows:

await FromEvent(new MyClass());

The problem is that you need to create a new FromEvent method for every event in every class that you would like to await on. That could get really large really quick, and it’s mostly just boilerplate code anyway.

Ideally I would like to be able to do something like this:

await FromEvent(new MyClass().OnCompletion);

Then I could re-use the same FromEvent method for any event on any instance. I’ve spent some time trying to create such a method, and there are a number of snags. For the code above it will generate the following error:

The event ‘Namespace.MyClass.OnCompletion’ can only appear on the left hand side of += or -=

As far as I can tell, there won’t ever be a way of passing the event like this through code.

So, the next best thing seemed to be trying to pass the event name as a string:

await FromEvent(new MyClass(), "OnCompletion");

It’s not as ideal; you don’t get intellisense and would get a runtime error if the event doesn’t exist for that type, but it could still be more useful than tons of FromEvent methods.

So it’s easy enough to use reflection and GetEvent(eventName) to get the EventInfo object. The next problem is that the delegate of that event isn’t known (and needs to be able to vary) at runtime. That makes adding an event handler hard, because we need to dynamically create a method at runtime, matching a given signature (but ignoring all parameters) that accesses a TaskCompletionSource that we already have and sets its result.

Fortunately I found this link which contains instructions on how to do [almost] exactly that via Reflection.Emit. Now the problem is that we need to emit IL, and I have no idea how to access the tcs instance that I have.

Below is the progress that I’ve made towards finishing this:

public static Task FromEvent<T>(this T obj, string eventName)
{
    var tcs = new TaskCompletionSource<object>();
    var eventInfo = obj.GetType().GetEvent(eventName);

    Type eventDelegate = eventInfo.EventHandlerType;

    Type[] parameterTypes = GetDelegateParameterTypes(eventDelegate);
    DynamicMethod handler = new DynamicMethod("unnamed", null, parameterTypes);

    ILGenerator ilgen = handler.GetILGenerator();

    //TODO ilgen.Emit calls go here

    Delegate dEmitted = handler.CreateDelegate(eventDelegate);

    eventInfo.AddEventHandler(obj, dEmitted);

    return tcs.Task;
}

What IL could I possibly emit that would allow me to set the result of the TaskCompletionSource? Or, alternatively, is there another approach to creating a method that returns a Task for any arbitrary event from an arbitrary type?

  • 1 1 Answer
  • 0 Views
  • 0 Followers
  • 0
Share
  • Facebook
  • Report

Leave an answer
Cancel reply

You must login to add an answer.

Forgot Password?

Need An Account, Sign Up Here

1 Answer

  • Voted
  • Oldest
  • Recent
  • Random
  1. Editorial Team
    Editorial Team
    2026-06-12T21:35:46+00:00Added an answer on June 12, 2026 at 9:35 pm

    Here you go:

    internal class TaskCompletionSourceHolder
    {
        private readonly TaskCompletionSource<object[]> m_tcs;
    
        internal object Target { get; set; }
        internal EventInfo EventInfo { get; set; }
        internal Delegate Delegate { get; set; }
    
        internal TaskCompletionSourceHolder(TaskCompletionSource<object[]> tsc)
        {
            m_tcs = tsc;
        }
    
        private void SetResult(params object[] args)
        {
            // this method will be called from emitted IL
            // so we can set result here, unsubscribe from the event
            // or do whatever we want.
    
            // object[] args will contain arguments
            // passed to the event handler
            m_tcs.SetResult(args);
            EventInfo.RemoveEventHandler(Target, Delegate);
        }
    }
    
    public static class ExtensionMethods
    {
        private static Dictionary<Type, DynamicMethod> s_emittedHandlers =
            new Dictionary<Type, DynamicMethod>();
    
        private static void GetDelegateParameterAndReturnTypes(Type delegateType,
            out List<Type> parameterTypes, out Type returnType)
        {
            if (delegateType.BaseType != typeof(MulticastDelegate))
                throw new ArgumentException("delegateType is not a delegate");
    
            MethodInfo invoke = delegateType.GetMethod("Invoke");
            if (invoke == null)
                throw new ArgumentException("delegateType is not a delegate.");
    
            ParameterInfo[] parameters = invoke.GetParameters();
            parameterTypes = new List<Type>(parameters.Length);
            for (int i = 0; i < parameters.Length; i++)
                parameterTypes.Add(parameters[i].ParameterType);
    
            returnType = invoke.ReturnType;
        }
    
        public static Task<object[]> FromEvent<T>(this T obj, string eventName)
        {
            var tcs = new TaskCompletionSource<object[]>();
            var tcsh = new TaskCompletionSourceHolder(tcs);
    
            EventInfo eventInfo = obj.GetType().GetEvent(eventName);
            Type eventDelegateType = eventInfo.EventHandlerType;
    
            DynamicMethod handler;
            if (!s_emittedHandlers.TryGetValue(eventDelegateType, out handler))
            {
                Type returnType;
                List<Type> parameterTypes;
                GetDelegateParameterAndReturnTypes(eventDelegateType,
                    out parameterTypes, out returnType);
    
                if (returnType != typeof(void))
                    throw new NotSupportedException();
    
                Type tcshType = tcsh.GetType();
                MethodInfo setResultMethodInfo = tcshType.GetMethod(
                    "SetResult", BindingFlags.NonPublic | BindingFlags.Instance);
    
                // I'm going to create an instance-like method
                // so, first argument must an instance itself
                // i.e. TaskCompletionSourceHolder *this*
                parameterTypes.Insert(0, tcshType);
                Type[] parameterTypesAr = parameterTypes.ToArray();
    
                handler = new DynamicMethod("unnamed",
                    returnType, parameterTypesAr, tcshType);
    
                ILGenerator ilgen = handler.GetILGenerator();
    
                // declare local variable of type object[]
                LocalBuilder arr = ilgen.DeclareLocal(typeof(object[]));
                // push array's size onto the stack 
                ilgen.Emit(OpCodes.Ldc_I4, parameterTypesAr.Length - 1);
                // create an object array of the given size
                ilgen.Emit(OpCodes.Newarr, typeof(object));
                // and store it in the local variable
                ilgen.Emit(OpCodes.Stloc, arr);
    
                // iterate thru all arguments except the zero one (i.e. *this*)
                // and store them to the array
                for (int i = 1; i < parameterTypesAr.Length; i++)
                {
                    // push the array onto the stack
                    ilgen.Emit(OpCodes.Ldloc, arr);
                    // push the argument's index onto the stack
                    ilgen.Emit(OpCodes.Ldc_I4, i - 1);
                    // push the argument onto the stack
                    ilgen.Emit(OpCodes.Ldarg, i);
    
                    // check if it is of a value type
                    // and perform boxing if necessary
                    if (parameterTypesAr[i].IsValueType)
                        ilgen.Emit(OpCodes.Box, parameterTypesAr[i]);
    
                    // store the value to the argument's array
                    ilgen.Emit(OpCodes.Stelem, typeof(object));
                }
    
                // load zero-argument (i.e. *this*) onto the stack
                ilgen.Emit(OpCodes.Ldarg_0);
                // load the array onto the stack
                ilgen.Emit(OpCodes.Ldloc, arr);
                // call this.SetResult(arr);
                ilgen.Emit(OpCodes.Call, setResultMethodInfo);
                // and return
                ilgen.Emit(OpCodes.Ret);
    
                s_emittedHandlers.Add(eventDelegateType, handler);
            }
    
            Delegate dEmitted = handler.CreateDelegate(eventDelegateType, tcsh);
            tcsh.Target = obj;
            tcsh.EventInfo = eventInfo;
            tcsh.Delegate = dEmitted;
    
            eventInfo.AddEventHandler(obj, dEmitted);
            return tcs.Task;
        }
    }
    

    This code will work for almost all events that return void (regardless of the parameter list).

    It can be improved to support any return values if necessary.

    You can see the difference between Dax’s and mine methods below:

    static async void Run() {
        object[] result = await new MyClass().FromEvent("Fired");
        Console.WriteLine(string.Join(", ", result.Select(arg =>
            arg.ToString()).ToArray())); // 123, abcd
    }
    
    public class MyClass {
        public delegate void TwoThings(int x, string y);
    
        public MyClass() {
            new Thread(() => {
                    Thread.Sleep(1000);
                    Fired(123, "abcd");
                }).Start();
        }
    
        public event TwoThings Fired;
    }
    

    Briefly, my code supports really any kind of delegate type. You shouldn’t (and don’t need to) specify it explicitly like TaskFromEvent<int, string>.

    • 0
    • Reply
    • Share
      Share
      • Share on Facebook
      • Share on Twitter
      • Share on LinkedIn
      • Share on WhatsApp
      • Report

Sidebar

Related Questions

I'm having trouble using the new async/await tools in c#. Here is my scenario:
I'm trying out the new async and await keywords using VS2012RC and .NET 4.5
I am just using the new Async Controller features in MVC 4 as described
I'm getting an access violation on a char array I just created using new
I'm currently testing GAs new async code snippet using two different tracking codes on
I'm trying to learn about Async programming using VS2012 and its Async Await keyword.
I have installed Async-CTP-v3 for using new feature of C# 5 but when I
Using latest CTP5 with async/await keywords, I wrote some code, which apparently cannot compile:
For running code asynchronously (eg with async/await) I need a proper Task. Of course
Say I have a WPF dialog in which I have async event handlers that

Explore

  • Home
  • Add group
  • Groups page
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Users
  • Help
  • SEARCH

Footer

© 2021 The Archive Base. All Rights Reserved
With Love by The Archive Base

Insert/edit link

Enter the destination URL

Or link to existing content

    No search term specified. Showing recent items. Search or use up and down arrow keys to select an item.