2011-12-22 17 views
14

Lector de larga duración, primer póster aquí.Async/Espera la implementación de la clase WebBrowser para .NET

Mi objetivo: poder aprovechar async/await mientras utilizo la clase WebBrowser. Como WebBrowser.Navigate (url de cadena) es un método asíncrono, y no puede examinar el documento html hasta que se active el evento LoadComplete.

Aquí está mi código (de trabajo) hasta el momento:

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(); 
    } 
} 

Y esta clase anterior ahora me permite utilizar lo siguiente:

public async void NavigateToGoogle() { 
    await browser.NavigateAsync("www.google.com"); 
    //Do any necessary actions on google.com 
} 

Sin embargo, me pregunto si hay una mayor manera eficiente/adecuada de manejar esto. Específicamente, Task.Factory.CreateNew con el bloqueo ManualResetEvent. ¡Gracias por tu contribución!

Respuesta

12

En primer lugar, creo que este es un gran ejercicio para aprender cómo funciona async/await.

Pareces saltar por los aros para hacer que NavigateAsync devuelva una Tarea. ¡Pero no tiene que devolver una tarea para estar a la espera! Un método que contiene una espera debe devolver Tarea, pero un método que es aguardable no necesita devolver Tarea; todo lo que tiene que hacer es devolver algún tipo al que pueda llamar GetAwaiter.

Se podría considerar la implementación de un tipo poco como esto:

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

y ha NavigateAsync regresar algún tipo sobre el que se puede llamar GetAwaiter que devuelve un WebBrowserAwaiter. No es necesario crear una Tarea solo para obtener su método GetAwaiter cuando pueda hacer uno propio.

En términos más generales, algo que quizás desee considerar es ¿Qué sucede si hay una segunda llamada a NavigateAsync mientras la primera sigue navegando?

+0

Tiene toda la razón, había pasado bastante tiempo para que devolviera una Tarea correctamente que podría esperar. Jaja. Voy a jugar con esta sugerencia y ver si puedo encontrar algo. ¡Gracias! –

+0

Sean S, ¿ya has encontrado una buena solución? – Blaise

2

Puede usar TaskCompletionSource<T> para crear una Tarea y marcarla como completada más tarde.

No veo ninguna alternativa para la tarea no genérica, pero como Task<T> deriva de Task<T>, puede simplemente usar TaskCompletionSource<object> y establecer el resultado como nulo.

+0

Gracias por la sugerencia. Veré si esto puede usarse para mejorar el código. :) –

-1

Creé esta clase hoy, con la ayuda de otra publicación en stackoverflow, quiero tener el control de webbrowser listo sin ningún bloqueo de hilo usando (Async/Await).

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

Aquí es la clase:

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 
-1

traduje código de VB de Vaibhav a C#. es una solución increíble, no sé por qué te decepciona.

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, 
} 
Cuestiones relacionadas