Используя новую модель async/await, довольно просто сгенерировать Task
, которое завершается при срабатывании события; вам просто нужно следовать этому шаблону:
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;
}
Затем это позволяет:
await FromEvent(new MyClass());
Проблема в том, что вам нужно создать новый метод FromEvent
для каждого события в каждом классе, на котором вы хотели бы await
работать. Это может очень быстро стать очень большим, и в любом случае это в основном просто шаблонный код.
В идеале я хотел бы иметь возможность сделать что-то вроде этого:
await FromEvent(new MyClass().OnCompletion);
Затем я мог бы повторно использовать один и тот же метод FromEvent
для любого события в любом экземпляре. Я потратил некоторое время, пытаясь создать такой метод, и есть несколько загвоздок. Для приведенного выше кода будет выдана следующая ошибка:
Событие «Namespace.MyClass.OnCompletion» может отображаться только слева от += или -=
Насколько я могу судить, никогда не будет способа передать такое событие через код.
Итак, следующая лучшая вещь, похоже, — попытаться передать имя события в виде строки:
await FromEvent(new MyClass(), "OnCompletion");
Это не так идеально; вы не получаете intellisense и получите ошибку времени выполнения, если событие не существует для этого типа, но оно все же может быть более полезным, чем множество методов FromEvent.
Так что достаточно просто использовать отражение и GetEvent(eventName)
, чтобы получить объект EventInfo
. Следующая проблема заключается в том, что делегат этого события неизвестен (и должен иметь возможность изменяться) во время выполнения. Это затрудняет добавление обработчика событий, потому что нам нужно динамически создавать метод во время выполнения, соответствующий заданной сигнатуре (но игнорирующий все параметры), который обращается к уже имеющемуся TaskCompletionSource
и устанавливает его результат.
К счастью, я нашел эту ссылку, содержащую инструкции о том, как сделать [почти] именно так через Reflection.Emit
. Теперь проблема в том, что нам нужно сгенерировать IL, и я понятия не имею, как получить доступ к экземпляру tcs
, который у меня есть.
Ниже показан прогресс, которого я добился в завершении этого:
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;
}
Какой IL я мог бы сгенерировать, что позволило бы мне установить результат TaskCompletionSource
? Или, как вариант, есть другой подход к созданию метода, возвращающего Task для любого произвольного события из произвольного типа?
TaskFactory.FromAsync
для простого преобразования из APM в TAP. Не существует простого и универсального способа перевода из EAP в TAP, поэтому я думаю, что именно поэтому MS не включила подобное решение. Я считаю, что Rx (или поток данных TPL) в любом случае лучше соответствует семантике событий, и Rx действительно имеет метод типаFromEvent
. - person Stephen Cleary   schedule 13.10.2012FromEvent<>
, и это близко, поскольку я мог добраться до этого без использования отражения. - person noseratio   schedule 24.07.2015