防止WebBrowser在导航时导致UI冻结?

时间:2018-03-04 15:16:33

标签: c# .net winforms asynchronous webbrowser-control

我面临的问题是,在处理WebBrowser控件时(无论是否可见),它会导致UI在导航时冻结一小段时间,当必须按顺序打开几个URL时,这变得非常明显和不可靠。

我目前正在使用Noseratio's NavigateAsync扩展方法以静默方式导航到多个网址。异步:(随意跳过阅读代码并继续提问)

public static async Task<string> NavigateAsync(this WebBrowser webBrowser, string url, CancellationToken token)
{
    var tcs = new TaskCompletionSource<bool>();
    WebBrowserDocumentCompletedEventHandler handler = (s, arg) => tcs.TrySetResult(true);

    using (token.Register(() => { webBrowser.Stop(); tcs.TrySetCanceled(); }, true))
    {
        webBrowser.DocumentCompleted += handler;
        try
        {
            webBrowser.Navigate(url);
            await tcs.Task; // wait for DocumentCompleted
        }
        finally
        {
            webBrowser.DocumentCompleted -= handler;
        }
    }

    var documentElement = webBrowser.Document.GetElementsByTagName("html")[0];
    var html = documentElement.OuterHtml;
    while (true)
    {
        await Task.Delay(POLL_DELAY, token);
        if (webBrowser.IsBusy)
            continue;

        var htmlNow = documentElement.OuterHtml;
        if (html == htmlNow) break; 

        html = htmlNow;
    }

    token.ThrowIfCancellationRequested();
    return html;
}

但即便是最简单的代码,如下所示:

WebBrowser wb = new WebBrowser() { ScriptErrorsSuppressed = true };
wb.Navigate("https://www.google.com/");

..仍然有同样的效果。

这是一个快速demo video,可以用最简单的代码显示问题。

我也试过让WebBrowser在不同的STA线程上运行,但仍然没有运气。

那么,有没有办法在处理WebBrowser时避免冻结?

在您需要建议将HttpClientWebClient替换为HTMLAgilityPack之前,请注意我正在使用WebBrowser来获取显示的文本,格式尽可能接近它是如何在浏览器中显示的(即,尽可能接近手动选择和复制文本)。我尝试(或在线找到)而不使用浏览器的每个解决方案都无法实现这一点,即使the one that produced the closest result也不够好。

1 个答案:

答案 0 :(得分:0)

我可以确认您何时加载WebBrowser控件,用户界面冻结片刻,如果您使用WebBrowser控件的多个实例加载多个网址,则滞后的用户界面很烦人,您不能与主窗口交互。

要重现此问题,您可以使用以下代码:

string google = "http://www.google.com";
var urls = Enumerable.Range(1, 100).Select(x => google).ToList();
foreach (var url in urls)
{
    var w = new WebBrowser() { ScriptErrorsSuppressed = true };
    w.DocumentCompleted += (obj, args) =>
        {
            var txt = ((WebBrowser)obj).DocumentText;
            this.textBox1.Text = DateTime.Now.ToString() + Environment.NewLine
                + txt.Substring(1, 200) + "...";
        };
    w.Navigate(url);
}

要解决此问题,您可以创建一个方法,在另一个线程中加载WebBrowser控件并返回Task<string>,该文件在浏览器文档完成时完成。我在this post中创建了BrowserBasedWebScraper,您可以使用它来获取场景后WebBrowser控件的内容,而不会滞后用户界面:

string google = "http://www.google.com";
var urls = Enumerable.Range(1, 100).Select(x => google).ToList();
foreach (var url in urls)
{
    var txt = await BrowserBasedWebScraper.LoadUrl(url);
    this.textBox1.Text = DateTime.Now.ToString() + Environment.NewLine
        + txt.Substring(1, 200) + "...";
}

您还可以download repository中的expects作为示例。