异步/等待.NET的WebBrowser类的实现

长期读者,这里的第一次海报。

我的目标:在使用WebBrowser类时能够利用async / await。 由于WebBrowser.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返回任务。 但它不必返回任务以便等待! 包含await的方法必须返回Task,但是一个等待的方法不需要返回Task; 它所要做的就是返回一些你可以调用GetAwaiter的类型。

你可以考虑实现这样的小类:

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

并让NavigateAsync返回一些类型,您可以在其上调用返回WebBrowserAwaiter的GetAwaiter。 当你可以制作自己的GetAwaiter方法时,无需构建任务。

更一般地说,您可能想要考虑的事情,如果第一次调用NavigateAsync而第一个仍然在导航时会发生什么?

您可以使用TaskCompletionSource创建一个Task,并将其标记为稍后完成。

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

我今天创建了这个类,在stackoverflow的另一个post的帮助下,我希望得到准备好的webbrowser控件,而不使用任何线程阻塞(Async / Await)。

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

这是class级:

 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 

我将Vaibhav的VB代码翻译成C#。 这是一个了不起的解决方案,我不知道为什么你会对此感到失望。

 public class YourClassThatIsUsingWebBrowser : IDisposable { private WebBrowser browser; private TaskCompletionSource 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(); } 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 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, }