Async/Ожидание реализации класса WebBrowser для .NET.
Долгое время читатель, первый плакат здесь.
Моя цель: иметь возможность использовать async/wait при использовании класса WebBrowser. Поскольку WebBrowser.Navigate(строковый url) является асинхронным методом, и вы не можете исследовать html-документ до тех пор, пока не будет запущено событие LoadComplete.
Вот мой (рабочий) код:
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. Спасибо за ваш вклад!
Ответы
Ответ 1
Во-первых, я думаю, что это отличное упражнение для изучения того, как работает async/await.
Вы, кажется, прыгаете через обручи, чтобы NavigateAsync возвращал задачу. Но он не должен возвращать Задачу, чтобы быть в ожидании! Метод, который содержит ожидание, должен возвращать Задачу, но метод, который является ожидаемым, не должен возвращать Задачу; все, что ему нужно сделать, это вернуть какой-то тип, который можно вызвать GetAwaiter.
Вы можете рассмотреть возможность создания небольшого типа, подобного этому:
public struct WebBrowserAwaiter<T>
{
public bool IsCompleted { get { ... } }
public void OnCompleted(Action continuation) { ... }
public T GetResult() { ... }
}
и NavigateAsync возвращает некоторый тип, на который вы можете вызвать GetAwaiter, который возвращает WebBrowserAwaiter. Не нужно создавать задачу только для получения метода GetAwaiter, когда вы можете сделать свой собственный.
В общем, что-то, о чем вы могли бы подумать, - это то, что происходит, если есть второй вызов NavigateAsync, в то время как первый по-прежнему перемещается?
Ответ 2
Вы можете использовать TaskCompletionSource<T>
для создания задачи и пометить ее как выполненную позже.
Я не вижу альтернативы для не-общей задачи, но поскольку Task<T>
происходит от Task
, вы можете просто использовать TaskCompletionSource<object>
и установить для результата значение null.
Ответ 3
Я создал этот класс сегодня, с помощью другого сообщения в stackoverflow, я хочу получить готовый элемент управления webbrowser без блокировки потоков с помощью (Async/Await).
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
Ответ 4
Я перевел код Vaibhav VB на С#. это удивительное решение, я не знаю, почему вы разочаровываетесь в этом.
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,
}