Longtime reader, first-time poster here.
My goal: To be able to take advantage of async/await while using the WebBrowser class. As the WebBrowser.Navigate(string ur
I created this class today, with the help of another post on stackoverflow, I want to get the ready webbrowser control without any thread blocking using (Async/Await).
Dim bb = New wbBrowser
Dim wb = Await bb.GetBrowserAsync("http://www.msn.com")
Here is the 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
First off, I think this is a great exercise for learning how async/await works.
You seem to be jumping through hoops in order to make NavigateAsync return a Task. But it doesn't have to return a Task in order to be awaitable! A method that contains an await must return Task, but a method that is awaitable need not return Task; all it has to do is return some type that you can call GetAwaiter on.
You might consider implementing a little type like this:
public struct WebBrowserAwaiter<T>
{
public bool IsCompleted { get { ... } }
public void OnCompleted(Action continuation) { ... }
public T GetResult() { ... }
}
and have NavigateAsync return some type upon which you can call GetAwaiter that returns a WebBrowserAwaiter. No need to build up a Task just to get its GetAwaiter method when you can make your own.
More generally, something you might want to give some thought to is what happens if there is a second call to NavigateAsync while the first one is still navigating?
You can use TaskCompletionSource<T>
to create a Task and mark it as completed later.
I don't see any alternative for the non-generic task, but as Task<T>
derives from Task
, you could just use a TaskCompletionSource<object>
and set the result to null.
I translated Vaibhav's VB code to C#. it's an amazing solution, I don't know why you are disappoint on it.
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,
}