防止 WebBrowser 在导航时导致 UI 冻结?
Prevent WebBrowser from causing the UI to freeze while navigating?
我面临的问题是,在处理 WebBrowser
控件(无论它是否可见)时,它会导致 UI 冻结一小段时间 在导航时,这在必须顺序打开多个 URL 时变得非常明显和不可靠。
我目前正在使用 Noseratio's NavigateAsync
扩展方法静默和异步地导航到多个 URL:(请跳过阅读代码并继续问题)
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 显示问题的最简单的代码。
我也试过在不同的 STA 线程上使用 WebBrowser 运行,但还是不行。
那么,有没有办法在处理 WebBrowser
时避免冻结?
在您费心建议将其替换为 HttpClient
或将 WebClient
替换为 HTMLAgilityPack
之前,请注意我正在使用 WebBrowser 以获取显示的文本,格式接近尽可能接近它在浏览器中的显示方式(即尽可能接近手动选择和复制文本)。我在不使用浏览器的情况下尝试(或在网上找到)的每个解决方案 都无法实现,甚至 the one that produced the closest result 也不够好。
我可以确认当您加载 WebBrowser
控件时,UI 会冻结片刻,如果您使用 WebBrowser
控件的多个实例加载多个 url,滞后的 UI 很烦人,你不能与主要 window 互动。
要重现问题,您可以使用以下代码:
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
控件,并在浏览器文档完成时 return 一个 Task<string>
完成。我在 中创建了一个 BrowserBasedWebScraper
,您可以使用它在幕后获取 WebBrowser
控件的内容而不会滞后 UI:
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 a working example from this repository.
我面临的问题是,在处理 WebBrowser
控件(无论它是否可见)时,它会导致 UI 冻结一小段时间 在导航时,这在必须顺序打开多个 URL 时变得非常明显和不可靠。
我目前正在使用 Noseratio's NavigateAsync
扩展方法静默和异步地导航到多个 URL:(请跳过阅读代码并继续问题)
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 显示问题的最简单的代码。
我也试过在不同的 STA 线程上使用 WebBrowser 运行,但还是不行。
那么,有没有办法在处理 WebBrowser
时避免冻结?
在您费心建议将其替换为 HttpClient
或将 WebClient
替换为 HTMLAgilityPack
之前,请注意我正在使用 WebBrowser 以获取显示的文本,格式接近尽可能接近它在浏览器中的显示方式(即尽可能接近手动选择和复制文本)。我在不使用浏览器的情况下尝试(或在网上找到)的每个解决方案 都无法实现,甚至 the one that produced the closest result 也不够好。
我可以确认当您加载 WebBrowser
控件时,UI 会冻结片刻,如果您使用 WebBrowser
控件的多个实例加载多个 url,滞后的 UI 很烦人,你不能与主要 window 互动。
要重现问题,您可以使用以下代码:
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
控件,并在浏览器文档完成时 return 一个 Task<string>
完成。我在 BrowserBasedWebScraper
,您可以使用它在幕后获取 WebBrowser
控件的内容而不会滞后 UI:
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 a working example from this repository.