【问题标题】:Async/Await implementation of WebBrowser class for .NET.NET 的 WebBrowser 类的异步/等待实现
【发布时间】:2012-01-26 11:13:33
【问题描述】:

老读者,第一次发帖。

我的目标:在使用 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。感谢您的意见!

【问题讨论】:

    标签: c# .net asynchronous browser async-await


    【解决方案1】:

    首先,我认为这是学习 async/await 工作原理的一个很好的练习。

    您似乎是为了让 NavigateAsync 返回一个任务而越过障碍。但它不必为了等待而返回任务!一个包含await的方法必须返回Task,但是一个可等待的方法不需要返回Task;它所要做的就是返回一些可以调用 GetAwaiter 的类型。

    你可以考虑实现一个像这样的小类型:

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

    并让 NavigateAsync 返回某种类型,您可以在该类型上调用返回 WebBrowserAwaiter 的 GetAwaiter。当您可以自己创建任务时,无需构建一个任务来获取它的 GetAwaiter 方法。

    更一般地说,您可能需要考虑一下如果第二次调用 NavigateAsync 而第一次调用仍在导航时会发生什么?

    【讨论】:

    • 你说得非常对,我花了相当长的时间让它正确返回一个任务,然后我可以等待。哈哈。我会尝试这个建议,看看我能不能想出一些东西。谢谢!
    【解决方案2】:

    您可以使用TaskCompletionSource&lt;T&gt; 来创建一个任务并将其标记为稍后完成。

    我没有看到非通用任务的任何替代方案,但由于 Task&lt;T&gt; 派生自 Task,您可以只使用 TaskCompletionSource&lt;object&gt; 并将结果设置为 null。

    【讨论】:

    • 感谢您的建议。我会看看这是否可以用来改进代码。 :)
    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

      我今天创建了这个类,在 stackoverflow 上的另一篇文章的帮助下,我想使用 (Async/Await) 获得准备好的 webbrowser 控件,而无需任何线程阻塞。

      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
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-04-05
        • 1970-01-01
        • 2015-10-06
        • 1970-01-01
        • 2013-11-13
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多