3

I know this is slightly a duplicate of this question here: Blocking and waiting for an event

However, I was in the process of writing a EventWaiter and ran into a problem. Here is a (majorly) simplified version of what I've been working on:

public class EventWaiter
{
    private AutoResetEvent _autoResetEvent = new AutoResetEvent(false);
    private EventInfo _event = null;
    private object _eventContainer = null;

    public EventWaiter(object eventContainer, string eventName)
    {
        _eventContainer = eventContainer;
        _event = eventContainer.GetType().GetEvent(eventName);
    }
    public void WaitForEvent()
    {
        MethodInfo method = this.GetType().GetMethod("DynamicCaller");
        Delegate handler = Delegate.CreateDelegate(this._event.EventHandlerType, this, method);

        _event.AddEventHandler(_eventContainer, handler);

        _autoResetEvent.WaitOne();

        _event.RemoveEventHandler(_eventContainer, _handler);

    }
    public void DynamicCaller(/* insert magic here */)
    {
        _autoResetEvent.Set();
    }
}

The usage would simply be:

EventWaiter ew = new EventWaiter(someClass, "someEvent");
ew.WaitForEvent();

Basically what is happening, is its registering the DynamicCaller void as a handler for this event. The problem is, events have different signatures, and I want to be able to handle the event regardless of the delegate used.

I can get the type of the delegate with this._event.EventHandlerType but how can I use to that create a completely reusable class no matter what the delegate is? If the DynamicCaller parameters are not exactly the same as the event delegate parameters i get an exception.

As a side note, I did a bunch of looking into code in the framework, and if i had access to some of that I think this would be easy. Too bad that alot of the classes I would need are all internal to the framework.

Community
  • 1
  • 1
caesay
  • 16,932
  • 15
  • 95
  • 160
  • I think you might want to have a look at TaskCompletionSource http://msdn.microsoft.com/en-us/library/dd449174.aspx – Peter Ritchie Aug 12 '12 at 17:45
  • @PeterRitchie: I had a look at it, and I can see the usefulness of it, but I fail to see how that is related to this. It can not be used in substitution for my solution, and it doesn't help in solving my problem here. Perhaps a little more information as to why you suggested it would be helpful :) – caesay Aug 12 '12 at 17:47
  • I don't understand why you can't template the delegate type? – Hogan Aug 12 '12 at 17:56

4 Answers4

2

Since all events that respect the recommended pattern have a parameter of type object and a parameter of a type that derives from EventArgs, you should be able to handle all these events with this signature:

void DynamicCaller(object sender, EventArgs e)

Of course it won't work for non-standard event signatures...


EDIT: here's an example with a dynamically generated handler:

public class EventWaiter
{
    private AutoResetEvent _autoResetEvent = new AutoResetEvent(false);
    private EventInfo _event = null;
    private object _eventContainer = null;

    public EventWaiter(object eventContainer, string eventName)
    {
        _eventContainer = eventContainer;
        _event = eventContainer.GetType().GetEvent(eventName);
    }
    public void WaitForEvent()
    {
        Delegate handler = CreateHandler();

        _event.AddEventHandler(_eventContainer, handler);

        _autoResetEvent.WaitOne();

        _event.RemoveEventHandler(_eventContainer, handler);

    }

    private Delegate CreateHandler()
    {
        var invokeMethod = _event.EventHandlerType.GetMethod("Invoke");
        var invokeParameters = invokeMethod.GetParameters();
        var handlerParameters = invokeParameters.Select(p => Expression.Parameter(p.ParameterType, p.Name)).ToArray();
        var body = Expression.Call(Expression.Constant(_autoResetEvent), "Set", null);
        var handlerExpression = Expression.Lambda(_event.EventHandlerType, body, handlerParameters);
        return handlerExpression.Compile();
    }
}

EDIT: SLaks was faster than me ;)

Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
  • Yes and no, Although that is recommended pattern, and everything is supposed to follow that, not everything does - which is where the problem comes from, because i want to be able to handle that odd case. – caesay Aug 12 '12 at 17:54
  • What about possibly using a DynamicMethod and Emitting to it? – caesay Aug 12 '12 at 17:57
  • 1
    @caesay, yes, that's what I meant by "dynamic code generation" (although you can also use Linq expressions) – Thomas Levesque Aug 12 '12 at 17:58
1

You should use expression trees to compile a method with an arbitrary set of parameters that calls your callback:

Expression.Lambda(
    _event.EventHandlerType,

    Expression.Call(Exrpession.Constant(_autoResetEvent), 
                    typeof(AutoResetEvent).GetMethod("Set")),

    _event.EventHandlerType.GetMethod("Invoke")
                           .GetParameters()
                           .Select(p => Expression.Parameter(p.ParameterType))
).Compile();

Note that you can make your system type-safe using generics and expression trees:

 new EventWaiter(_ => someObject.SomeEvent += _)

Where _ is an ordinary (but short) parameter name.

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
0

You could do what you want with TaskCompletionSource:

  TaskCompletionSource<string> tcs = 
    new TaskCompletionSource<string>();

  WebClient client = new WebClient();

  client.DownloadStringCompleted += (sender, args) => {
    if (args.Error != null) tcs.SetException(args.Error);
    else if (args.Cancelled) tcs.SetCanceled();
    else tcs.SetResult(args.Result);
  };

  client.DownloadStringAsync(address);

  tcs.Task.Wait(); // WaitForEvent
Peter Ritchie
  • 35,463
  • 9
  • 80
  • 98
  • This still has nothing to do with what I'm trying to do. – caesay Aug 12 '12 at 18:07
  • You've described that you want to wait for an event to occur. That code does that. If you in fact don't want to wait for an event to occur, could you please clarify. – Peter Ritchie Aug 12 '12 at 18:11
  • Im trying to wait for an event yes, but I would like to do it in a way that can be abstracted away into a helper class. – caesay Aug 12 '12 at 18:14
0

The solutions here are good, but for me, using strings, reflection has a bit of a code smell, so I'll go for a generic version:

public class EventWaiter
{
    public enum Mode
    {
        Wait,
        Detach
    }

    public static Func<Mode, TEventArgs> Create<TDelegate, TEventArgs>(
        Func<Action<object, TEventArgs>, TDelegate> converter,
        Action<TDelegate> addHandler,
        Action<TDelegate> removeHandler
        )
    {
        AutoResetEvent semaphore = new AutoResetEvent(false);
        TEventArgs args = default(TEventArgs);

        TDelegate handler = converter((s, e) => {  args = e; semaphore.Set(); });

        addHandler(handler);

        return mode =>
        {
            if (mode == Mode.Wait)
            {
                semaphore.WaitOne(); 
                return args;
            }
            else
            {
                removeHandler(handler);
                return default(TEventArgs);
            }
        };
    }

Usage:

        var evt = 
        EventWaiter.Create<SerialDataReceivedEventHandler, SerialDataReceivedEventArgs>
            (handler => (s, e) => handler(s, e), 
            h => port.DataReceived += h,
            h => port.DataReceived -= h);

        var firstArgument = evt(EventWaiter.Mode.Wait); //Wait for first event
        var secondArgument = evt(EventWaiter.Mode.Wait); //Wait for second event

        evt(EventWaiter.Mode.Detach); //Dispose
Asti
  • 12,447
  • 29
  • 38