将事件转换为任务的可重用模式

nos*_*tio 14 .net c# generics events async-await

我想有一个通用的可重用的代码为包装EAP模式的任务,类似什么东西Task.Factory.FromAsync了呢BeginXXX/EndXXXAPM模式.

例如:

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" });
}
Run Code Online (Sandbox Code Playgroud)

到目前为止,它看起来像这样:

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

是否有可能提出类似的东西,但要求我输入WebBrowserDocumentCompletedEventHandler两次(for registerEvent/ unregisterEvent),而不诉诸反射?

max*_*max 9

可以使用辅助类和流畅的语法:

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在你有一个WithHandlerConversion辅助方法,它可以从converter参数中推断出类型参数,这意味着你只需要写WebBrowserDocumentCompletedEventHandler一次.用法:

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);
Run Code Online (Sandbox Code Playgroud)