将事件转换为任务的可重用模式
我想为提供通用的可重复使用的代码段将EAP模式包装为任务,类似于 Task.Factory.FromAsync
对 BeginXXX / EndXXX
APM模式。
I'd like to have a generic reusable piece of code for wrapping EAP pattern as task, something similar to what Task.Factory.FromAsync
does for BeginXXX/EndXXX
APM pattern.
例如:
private async void Form1_Load(object sender, EventArgs e)
{
await TaskExt.FromEvent<EventArgs>(
handler => this.webBrowser.DocumentCompleted +=
new WebBrowserDocumentCompletedEventHandler(handler),
() => this.webBrowser.Navigate("about:blank"),
handler => this.webBrowser.DocumentCompleted -=
new WebBrowserDocumentCompletedEventHandler(handler),
CancellationToken.None);
this.webBrowser.Document.InvokeScript("setTimeout",
new[] { "document.body.style.backgroundColor = 'yellow'", "1" });
}
到目前为止,它看起来像这样:
So far, it looks like this:
public static class TaskExt
{
public static async Task<TEventArgs> FromEvent<TEventArgs>(
Action<EventHandler<TEventArgs>> registerEvent,
Action action,
Action<EventHandler<TEventArgs>> unregisterEvent,
CancellationToken token)
{
var tcs = new TaskCompletionSource<TEventArgs>();
EventHandler<TEventArgs> handler = (sender, args) =>
tcs.TrySetResult(args);
registerEvent(handler);
try
{
using (token.Register(() => tcs.SetCanceled()))
{
action();
return await tcs.Task;
}
}
finally
{
unregisterEvent(handler);
}
}
}
是否有可能出现使用类似的东西,但是不需要我键入 WebBrowserDocumentCompletedEventHandler
两次(对于 registerEvent
/ unregisterEvent
),而不诉诸反思?
Is it possible to come up with something similar, which nevertheless would not require me to type WebBrowserDocumentCompletedEventHandler
twice (for registerEvent
/unregisterEvent
), without resorting to reflection?
有一个帮助类和流畅的语法:
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);
}
}
}
现在你有一个code> WithHandlerConversion helper方法,它可以从转换器参数推断类型参数,这意味着您只需要一次写入 WebBrowserDocumentCompletedEventHandler
。
用法:
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);