General Purpose Fromevent Method

General purpose FromEvent method

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

Async/Await + FromEvent method


How do I know which thread in my application the event will return?

You don't. You never do with events, unless the documentation for a specific event specifies the that it will be executed from the UI thread, a thread pool thread, etc.

I can somehow specify which thread will it continue?

If you want to run code in a UI thread then marshal to the UI thread in the event handler. If you want to run code in a thread pool thread then add a new task to the thread pool inside of the handler. Both of those tasks add overhead if not needed, so it's usually best to look at the documentation of the event to see which is needed.

However, in the case of the linked question, the whole idea is that you're no longer dealing with an event and an event handler, you're dealing with a Task. So if you add a continuation to the task, the question is where will that continuation run? That is entirely specified by you. You can use the default task scheduler and have it run in the thread pool, you can pass a UI SynchronizationContext to run in the UI thread, or you can just let it run wherever the task you are continuing runs. (Meaning you have no idea what thread will be running it.)

If you're using the task with await, then it will automatically configure the continuation to run in the synchronization context you were in before you started that async operation, which may or may not be the UI thread (but likely is). If you specifically don't want that, then use .ConfigureAwait(false);.

Is there any real benefit than using task.Wait() (if I do not have to worry about locking thread)?

The reason to use an asynchronous task based approach is that you're not blocking threads, particularly thread pool threads (since you've specifically said you're not blocking a UI, which is much worse). Having a thread sitting around doing nothing is a problem, in some environments more than others (such as ASP for a highly active site). By not doing a blocking wait, you aren't consuming those resources.

Observable.FromEvent custom event transform won't compile with error Method name expected

In the link I provided that I used as a model for my solution I missed seeing the last two lines of the FromEvent declaration as follows:

handler => axCZKEM.OnAttTransactionEx += handler,
handler => axCZKEM.OnAttTransactionEx -= handler);

the axCZKEM object is the event source.

In the system I am trying to construct that source already exists but I didn't create that in the example in my OP. To correct that problem I borrowed an example of a manufactured event source from here: How to use Observable.FromEvent instead of FromEventPattern and avoid string literal event names

The above link is a good source of general information on Events and FromEvent method.

See class Foo for a pretend event source.

I rewrote the transform a little to get the code below.
I didn't have to put a new on the event source within the FromEvent method and needed to write an "Event Handler" called ShowParamList to prove to myself that I could access the various values supplied by the event.

using System;
using System.Reactive.Linq;

namespace ObservableTest
{
internal class Program
{
public delegate void EventTest(int vs, string bs);
internal class TestParamList { public int vs1; public string bs1; }
static void Main(string[] args)
{
var EventSource = new TestEventSource();
var obs_test = Observable.FromEvent<EventTest, TestParamList>(
OnNext => (int vs, string bs) => OnNext(new TestParamList { vs1 = vs, bs1 = bs }),
h => EventSource.MyEvent += h,
h => EventSource.MyEvent -= h);

obs_test.Subscribe(x => ShowParamList(x));
EventSource.RaiseEvent(1, "first");
EventSource.RaiseEvent(2, "second");

Console.ReadKey();
}
static void ShowParamList(TestParamList t)
{
Console.WriteLine("Int: " + t.vs1.ToString() + " String: " + t.bs1.ToString());
}
public class TestEventSource
{
private EventTest delegateChain;
public event EventTest MyEvent
{
add
{
delegateChain += value;
}
remove
{
delegateChain -= value;
}
}
public void RaiseEvent(int x, string y)
{
var temp = delegateChain;
if (temp != null)
{
delegateChain(x, y);
}
}
}
}
}

Call a method when a generic event occurs

I finally managed to resolve the problem by slightly modifying the DynamicMethod definition, as described in this answer:

Reference 'this' in dynamic event handler

If I add my class type as first value in the Type array, which is then passed as parameter to the DynamicMethod, the delegate is correctly created and it works with instance methods!

public class Operation
{
public bool EventFired
{
get { return _eventFired; }
}

private bool _eventFired = false;

public void Execute(object source, string EventName)
{
EventInfo eventInfo = source.GetType().GetEvent(EventName);
Delegate handler = null;

Type delegateHandler = eventInfo.EventHandlerType;
MethodInfo invokeMethod = delegateHandler.GetMethod("Invoke");
ParameterInfo[] parms = invokeMethod.GetParameters();

Type[] parmTypes = new Type[parms.Length + 1];

parmTypes[0] = this.GetType(); //First parameter is this class type.

for (int i = 0; i < parms.Length; i++)
parmTypes[i + 1] = parms[i].ParameterType;

DynamicMethod customMethod = new DynamicMethod
(
"TempMethod",
invokeMethod.ReturnType,
parmTypes
);

MethodInfo inf = typeof (Operation).GetMethod("SetFlag", BindingFlags.Instance | BindingFlags.Public);

ILGenerator ilgen = customMethod.GetILGenerator();
ilgen.Emit(OpCodes.Ldarg_0);
ilgen.Emit(OpCodes.Call, inf);
ilgen.Emit(OpCodes.Ret);

handler = customMethod.CreateDelegate(delegateHandler, this);

eventInfo.AddEventHandler(source, handler);
}

/// <summary>Signals that the event has been raised.</summary>
public void SetFlag()
{
_eventFired = true;
}

}

A reusable pattern to convert event into task

It is possible with a helper class and a fluent-like syntax:

public static class TaskExt
{
public static EAPTask<TEventArgs, EventHandler<TEventArgs>> FromEvent<TEventArgs>()
{
var tcs = new TaskCompletionSource<TEventArgs>();
var handler = new EventHandler<TEventArgs>((s, e) => tcs.TrySetResult(e));
return new EAPTask<TEventArgs, EventHandler<TEventArgs>>(tcs, handler);
}
}


public sealed class EAPTask<TEventArgs, TEventHandler>
where TEventHandler : class
{
private readonly TaskCompletionSource<TEventArgs> _completionSource;
private readonly TEventHandler _eventHandler;

public EAPTask(
TaskCompletionSource<TEventArgs> completionSource,
TEventHandler eventHandler)
{
_completionSource = completionSource;
_eventHandler = eventHandler;
}

public EAPTask<TEventArgs, TOtherEventHandler> WithHandlerConversion<TOtherEventHandler>(
Converter<TEventHandler, TOtherEventHandler> converter)
where TOtherEventHandler : class
{
return new EAPTask<TEventArgs, TOtherEventHandler>(
_completionSource, converter(_eventHandler));
}

public async Task<TEventArgs> Start(
Action<TEventHandler> subscribe,
Action action,
Action<TEventHandler> unsubscribe,
CancellationToken cancellationToken)
{
subscribe(_eventHandler);
try
{
using(cancellationToken.Register(() => _completionSource.SetCanceled()))
{
action();
return await _completionSource.Task;
}
}
finally
{
unsubscribe(_eventHandler);
}
}
}

Now you have a WithHandlerConversion helper method, which can infer type parameter from converter argument, which means you need to write WebBrowserDocumentCompletedEventHandler only one time.
Usage:

await TaskExt
.FromEvent<WebBrowserDocumentCompletedEventArgs>()
.WithHandlerConversion(handler => new WebBrowserDocumentCompletedEventHandler(handler))
.Start(
handler => this.webBrowser.DocumentCompleted += handler,
() => this.webBrowser.Navigate(@"about:blank"),
handler => this.webBrowser.DocumentCompleted -= handler,
CancellationToken.None);

Pattern to use to handle a event with awaitable method in c#

Solution of @spender seems the cleanest and it works!
General purpose FromEvent method

Now my awaitable method
with this improvement TaskCompletionSource throws "An attempt was made to transition a task to a final state when it had already completed"
becomes:

public Task<TransactionData> PerformTransactionAwait()
{
var tcs = new TaskCompletionSource<TransactionData>();

EventHandler<TransactionInfo> callback = null;
callback = (sender, TransactionDataResult) =>
{
MyInterface.TransactionPerformed -= callback;
tcs.SetResult(TransactionDataResult);
};

MyInterface.TransactionPerformed += callback;
MyInterface.PerformTransactionAsync();

return tcs.Task;
}

Thank you! Lewix

Optimizing Observable.fromEvent on the window

If you need limit number of requests due to too many events try to use debounceTime operator.

https://rxjs-dev.firebaseapp.com/api/operators/debounce

Observable.fromEvent(window, 'wheel')
.debounceTime(300)
.subscribe(() => {
console.log(window)
})

Is it possible to target an EventHandler in a lambda expression?

No, it is not possible to target an event. Basically event is not a real type member, but just C# syntax which produces add_EventName and remove_EventName methods pair.

You could try refer to these internal methods name, but it's not possible in C# - http://msdn.microsoft.com/en-us/library/z47a7kdw.aspx

There are many similar questions in SO, with the same answer NO - like this one from Jon Skeet https://stackoverflow.com/a/4756021/2170171

If you're real crazy, you can try something like

private static void Subscribe(Action addHandler)
{
var IL = addHandler.Method.GetMethodBody().GetILAsByteArray();

// Magic here, in which we understand ClassName and EventName
???
}

with usage like

Subscribe(() => new Button().Click += null);

You could try using Cecil http://www.mono-project.com/Cecil for analyzing IL, or implement your own logic as it should not be too hard for predictable line of code.

I don't think that it is good solution though, as it just replaces one headache (proper event naming) with another one (proper Subscribe calling). Though, it will help with rename stuff.

add event listener in extension method

Action is an immutable type. You're not changing that action such that it performs different behavior when invoked, you're creating a new action (which you then do nothing with) that completes your TCS.

To generically be able to construct a Task based on any arbitrary event is rather hard, and doing it with complete type safety is impossible. You can see some of the difficulties with doing that here. Now to create a Task based on a particular event on a particular object, sure, that's easy (again, see the previously linked question).

This mostly stems from the feature that the C# language itself exposes around events simply not allowing for this.

Error creating Observable from Event

Most likely you need to do it like this:

var eventAsObservable = Observable.FromEvent<SensorValueChangedEventHandler, SensorValueChangedEventArgs>(
handler => (sender, args) => handler(args),
ev => CrossDeviceMotion.Current.SensorValueChanged += ev,
ev => CrossDeviceMotion.Current.SensorValueChanged -= ev);

In the first argument you tell how to map between Action<SensorValueChangedEventArgs> (basically OnNext) and your delegate. In second and third arguments you pass subscribe and unsubscribe functions.



Related Topics



Leave a reply



Submit