2011-12-22 20 views
14

Długoletni czytelnik, pierwszy plakat tutaj.Async/Oczekiwanie na implementację klasy WebBrowser dla .NET

Mój cel: Aby móc skorzystać z asynchronizacji/poczekać, korzystając z klasy WebBrowser. Ponieważ WebBrowser.Navigate (adres URL łańcucha) jest metodą asynchroniczną i nie można badać dokumentu HTML do momentu uruchomienia zdarzenia LoadComplete.

Oto moje (pracy) kod do tej pory:

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

A ta poprzednia klasa teraz pozwala mi korzystać następujące:

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

Jednak zastanawiam się, czy jest więcej skuteczny/właściwy sposób radzenia sobie z tym. W szczególności Task.Factory.CreateNew z blokowaniem ManualResetEvent. Dzięki za wkład!

Odpowiedz

12

Po pierwsze, myślę, że jest to świetne ćwiczenie do nauki, jak działa async/await.

Wygląda na to, że przeskakujesz przez obręcze, aby funkcja Navigate Async zwróciła zadanie. Ale nie musi zwracać Zadania, aby być godnym zaufania! Metoda, w której zawiera oczekujące, musi zwrócić zadanie, ale metoda, która jest jest oczekiwana nie musi zwracać Zadanie; wszystko, co musisz zrobić, to zwrócić jakiś typ, który możesz nazwać GetAwaiter.

Można rozważyć wdrożenie trochę typ tak:

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

i nie NavigateAsync powrócić pewnego rodzaju, na której można zadzwonić GetAwaiter zwracającą WebBrowserAwaiter. Nie musisz tworzyć zadania, aby uzyskać jego metodę GetAwaiter, gdy możesz tworzyć własne.

Bardziej ogólnie rzecz biorąc, warto zastanowić się, co to jest , co się stanie, gdy nastąpi drugie połączenie z NavigateAsync, podczas gdy pierwsza nadal nawiguje?

+0

Masz rację, spędziłem dość dużo czasu, aby zwrócić to zadanie poprawnie, a wtedy mogłem poczekać. Ha ha. Będę się bawił z tą sugestią i sprawdzę, czy coś wymyślę. Dzięki! –

+0

Sean S, czy masz już dobre rozwiązanie? – Blaise

2

Możesz użyć TaskCompletionSource<T>, aby utworzyć zadanie i oznaczyć je jako ukończone później.

Nie widzę alternatywy dla zadania nietypowego, ale jako Task<T> wywodzi się z Task, można po prostu użyć TaskCompletionSource<object> i ustawić wynik na wartość null.

+0

Dzięki za sugestię. Zobaczę, czy da się to w ogóle poprawić. :) –

-1

Stworzyłem tę klasę dzisiaj, z pomocą innego posta na stackoverflow, chcę uzyskać gotową kontrolę przeglądarki bez blokowania wątków za pomocą (Async/Await).

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

Oto klasa:

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

przetłumaczyłem Vaibhav za kod VB do C#. To niesamowite rozwiązanie, nie wiem, dlaczego jesteś rozczarowany.

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