WebBrowser类的Async/Await实现



老读者,第一次在这里发帖。

我的目标:在使用WebBrowser类时能够利用async/await的优势。作为网络浏览器。Navigate(string url)是一个异步方法,在LoadComplete事件被触发之前,你不能检查html文档。

这是我目前为止的(工作)代码:

public class AsyncWebBrowser
{
    protected WebBrowser m_WebBrowser;
    private ManualResetEvent m_MRE = new ManualResetEvent(false);
    public void SetBrowser(WebBrowser browser) {
        this.m_WebBrowser = browser;
        browser.LoadCompleted += new LoadCompletedEventHandler(WebBrowser_LoadCompleted);
    }
    public Task NavigateAsync(string url) {
        Navigate(url);
        return Task.Factory.StartNew((Action)(() => {
            m_MRE.WaitOne();
            m_MRE.Reset();
        }));
    }
    public void Navigate(string url) {
        m_WebBrowser.Navigate(new Uri(url));
    }
    void WebBrowser_LoadCompleted(object sender, NavigationEventArgs e) {
        m_MRE.Set();
    }
}

前面的类现在允许我使用以下内容:

public async void NavigateToGoogle() {
    await browser.NavigateAsync("www.google.com");
    //Do any necessary actions on google.com
}
然而,我想知道是否有更有效/适当的方法来处理这个问题。具体来说,是Task.Factory.CreateNew和阻塞的ManualResetEvent。谢谢你的建议!

首先,我认为这是学习async/await如何工作的一个很好的练习。

为了让NavigateAsync返回一个任务,你似乎是在跳圈。但是它不需要返回一个Task来等待!包含await的方法必须返回Task,但是是可等待的方法不需要返回Task;它所要做的就是返回一些你可以调用gettawaiter的类型。

你可以考虑实现这样一个小类型:

public struct WebBrowserAwaiter<T>
{
    public bool IsCompleted { get { ... } }
    public void OnCompleted(Action continuation) { ... }
    public T GetResult() { ... }
}

并让NavigateAsync返回一些类型,在这些类型上你可以调用返回WebBrowserAwaiter的GetAwaiter。当您可以创建自己的方法时,不需要仅仅为了获取它的GetAwaiter方法而构建一个Task。

更一般地说,你可能想要考虑的是如果有第二次调用NavigateAsync而第一个仍在导航会发生什么?

您可以使用TaskCompletionSource<T>创建一个任务,并将其标记为稍后完成。

我没有看到非通用任务的任何替代方案,但是由于Task<T>派生自Task,您可以使用TaskCompletionSource<object>并将结果设置为null。

我把Vaibhav的VB代码翻译成c#。这是一个神奇的解决办法,我不知道你为什么要失望。

public class YourClassThatIsUsingWebBrowser : IDisposable
{
    private WebBrowser browser;
    private TaskCompletionSource<BrowserResult> tcs;
    public YourClassThatIsUsingWebBrowser()
    {
        this.browser.DocumentCompleted += AsyncBrowser_DocumentCompleted;
        this.browser.Document.Window.Error += (errorSender, errorEvent) =>
        {
            SetResult(BrowserResult.Exception, errorEvent.Description);
        };
        this.browser.PreviewKeyDown += Browser_PreviewKeyDown;
        this.browser.Navigating += Browser_Navigating;
    }
    private void Browser_Navigating(object sender, WebBrowserNavigatingEventArgs e)
    {
        tcs = new TaskCompletionSource<BrowserResult>();
    }
    private void Browser_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
    {
        if (e.KeyCode == Keys.Escape)
        {
            this.browser.Stop();
            SetResult(BrowserResult.Cancelled);
        }
    }
    private void AsyncBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
    {
        SetResult();
    }

    public async Task<BrowserResult> NavigateAsync(string urlString)
    {
        this.browser.Navigate(urlString);
        return await tcs.Task;
    }
    private void SetResult(BrowserResult result = BrowserResult.Succeed, string error = null)
    {
        if (tcs == null)
        {
            return;
        }
        switch (result)
        {
            case BrowserResult.Cancelled:
                {
                    tcs.SetCanceled();
                    break;
                }
            case BrowserResult.Exception:
                {
                    tcs.SetException(new Exception(error));
                    break;
                }
            case BrowserResult.Succeed:
            default:
                {
                    tcs.SetResult(result);
                    break;
                }
        }
    }
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    bool disposed = false;
    protected void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                this.browser.Dispose();
            }
        }
        disposed = true;
    }
}
public enum BrowserResult
{
    Succeed,
    Cancelled,
    Exception,
}

我今天创建了这个类,在stackoverflow上的另一个帖子的帮助下,我想在没有任何线程阻塞的情况下使用(Async/Await)获得准备好的web浏览器控件。

Dim bb = New wbBrowser
Dim wb = Await bb.GetBrowserAsync("http://www.msn.com")

下面是类:

Imports System.Threading
Imports System.Threading.Tasks
Public Class wbBrowser
    Implements IDisposable
    Dim m_wbBrowser As New WebBrowser
    Dim m_tcs As TaskCompletionSource(Of WebBrowser)
    Public Sub New()
        m_wbBrowser.ScrollBarsEnabled = False
        m_wbBrowser.ScriptErrorsSuppressed = False
        AddHandler m_wbBrowser.DocumentCompleted, Sub(s, args) m_tcs.SetResult(m_wbBrowser)
    End Sub
    Public Async Function GetBrowserAsync(ByVal URL As String) As Task(Of WebBrowser)
        m_wbBrowser.Navigate(URL)
        Return Await WhenDocumentCompleted(m_wbBrowser)
    End Function
    Private Function WhenDocumentCompleted(browser As WebBrowser) As Task(Of WebBrowser)
        m_tcs = New TaskCompletionSource(Of WebBrowser)
        Return m_tcs.Task
    End Function
    Private disposedValue As Boolean
    Protected Overridable Sub Dispose(disposing As Boolean)
        If Not Me.disposedValue Then
            If disposing Then
                m_wbBrowser.Dispose()
            End If
        End If
        Me.disposedValue = True
    End Sub
    Public Sub Dispose() Implements IDisposable.Dispose
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub
End Class

最新更新