用于改进 C# 中的网络浏览器 URL 导航过程的线程概念
Threading concept to improve the webbrowsers URL navigation process in C#
我用 C# 应用程序开发了一个简单的 ID 检查 windows 表单,通过使用 webbrowser 控件传递到网页并获得回复来检查一组给定的 ID 是否有效,一切正常,它one.Now 花费 40 - 60 秒 20 Ids.one 我想使用 C# 中的高级线程概念来加速相同的过程。
代码运行良好我想提高使用线程的性能。今天任何简单的建议都会有很大帮助
private void button2_Click(object sender, EventArgs e)
{
string url = "https://idscheckingsite.com";
WebBrowser wb = new WebBrowser();
wb.ScriptErrorsSuppressed = true;
wb.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(Final_DocumentCompleted);
wb.Navigate(url);
}
private void Final_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
WebBrowser wbs = sender as WebBrowser;
wbs.Document.GetElementById("pannumber").InnerText = ListsofIds[ids];
wbs.Document.GetElementById("frmType1").SetAttribute("value", "24Q");
HtmlElement btnlink = wbs.Document.GetElementById("clickGo1");
btnlink.InvokeMember("Click");
//string response = wbs.DocumentText;
wbs.DocumentCompleted -= new WebBrowserDocumentCompletedEventHandler(Final_DocumentCompleted);
wbs.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(Final_result);
}
private void Final_result(object sender, WebBrowserDocumentCompletedEventArgs e)
{
WebBrowser wbResult = sender as WebBrowser;
string status = wbResult.Document.GetElementById("status").InnerText;
string name = wbResult.Document.GetElementById("name").InnerText;
wbResult.DocumentCompleted -= new WebBrowserDocumentCompletedEventHandler(Final_result);
wbResult.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(Final_DocumentCompleted);
DataRow dr = dt.NewRow();
dr[0] = PANNumber[ids];
dr[1] = status;
dr[2] = name;
dt.Rows.Add(dr);
++ids;
if (ids < 20)
wbResult.Navigate(vurl);
else
{
dataGridView1.DataSource = dt;
}
}
工作正常,但需要使用先进的 C# 线程概念(如果有的话)将性能提高到最大。
这是我的建议。单击按钮 2 时,将启动许多辅助任务。合理的数字是 4,但您可以尝试不同的数字,直到获得最佳性能。每个工作任务将使用自己的 WebBrowser
控件,并将调用 id 的子集。例如,工作任务 #0 将调用 ID 4、8、12、16 和 20,工作任务 #1 将调用 1、5、9、13 和 17 等。然后将等待所有工作任务完成, 然后可以更新 DataGridView
。不涉及多线程。一切都发生在 UI 线程中。不需要锁定或其他线程同步。
private async void button2_Click(object sender, EventArgs e)
{
string url = "https://idscheckingsite.com";
const int WORKER_TASKS_COUNT = 4;
var workerTasks = new Task[WORKER_TASKS_COUNT];
for (int i = 0; i < WORKER_TASKS_COUNT; i++)
{
workerTasks[i] = DoWorkAsync(i);
}
await Task.WhenAll(workerTasks);
dataGridView1.DataSource = dt;
async Task DoWorkAsync(int workerIndex)
{
using (var wb = new WebBrowser())
{
wb.ScriptErrorsSuppressed = true;
for (int i = 0; i < ListsofIds.Length; i++)
{
if (i % WORKER_TASKS_COUNT != workerIndex) continue;
wb.Navigate(url);
await wb; // await for the next DocumentCompleted
wb.Document.GetElementById("pannumber").InnerText = ListsofIds[i];
wb.Document.GetElementById("frmType1").SetAttribute("value", "24Q");
HtmlElement btnlink = wb.Document.GetElementById("clickGo1");
btnlink.InvokeMember("Click");
await wb; // await for the next DocumentCompleted
string status = wb.Document.GetElementById("status").InnerText;
string name = wb.Document.GetElementById("name").InnerText;
DataRow dr = dt.NewRow();
dr[0] = PANNumber[i];
dr[1] = status;
dr[2] = name;
dt.Rows.Add(dr);
}
}
}
}
上面的代码使用了一种有趣的技术来简化 WebBrowser
控件的导航。它不是手动订阅和取消订阅 DocumentCompleted
事件,而是通过 await
ing WebBrowser
控件自动执行。通常这是不可能的,但我们可以通过创建一个 returns a TaskAwaiter
:
的扩展方法来实现
public static class WebBrowserExtensions
{
public static TaskAwaiter<Uri> GetAwaiter(this WebBrowser wb)
{
var tcs = new TaskCompletionSource<Uri>();
WebBrowserDocumentCompletedEventHandler handler = null;
handler = (_, e) =>
{
wb.DocumentCompleted -= handler;
tcs.TrySetResult(e.Url);
};
wb.DocumentCompleted += handler;
return tcs.Task.GetAwaiter();
}
}
更新: 在自己使用我的代码后,我发现 await wb
有点混乱,因为 WebBrowser
控件有很多事件可以等待.因此,我通过创建事件的异步版本(而不是等待程序)使其更加明确和可扩展:
public static class WebBrowserExtensions
{
public static Task<Uri> DocumentCompletedAsync(this WebBrowser wb)
{
var tcs = new TaskCompletionSource<Uri>();
WebBrowserDocumentCompletedEventHandler handler = null;
handler = (_, e) =>
{
wb.DocumentCompleted -= handler;
tcs.TrySetResult(e.Url);
};
wb.DocumentCompleted += handler;
return tcs.Task;
}
}
可以这样使用:
await wb.DocumentCompletedAsync();
然后创建更多扩展方法变得微不足道,例如 NavigatedAsync
或 DocumentTitleChangedAsync
。
更新: 无休止地等待不是很好,因此可以在等待的扩展方法中添加超时(以毫秒表示)作为参数。由于整个代码旨在 运行 专门用于 UI 线程,因此我使用了 System.Windows.Forms.Timer
,尽管通常 CancellationToken
可能更方便。该代码有点涉及避免内存泄漏,对于打算 运行 数小时并执行数千个 Web 请求的应用程序来说,这可能是一个问题。
public static class WebBrowserExtensions
{
public static Task<Uri> DocumentCompletedAsync(this WebBrowser wb, int timeout)
{
var tcs = new TaskCompletionSource<Uri>();
WebBrowserDocumentCompletedEventHandler handler = null;
var timeoutRegistration = WithTimeout(tcs, timeout,
() => wb.DocumentCompleted -= handler);
handler = (_, e) =>
{
wb.DocumentCompleted -= handler;
timeoutRegistration.Unregister();
tcs.TrySetResult(e.Url);
};
wb.DocumentCompleted += handler;
return tcs.Task;
}
public static Task<Uri> DocumentCompletedAsync(this WebBrowser wb)
{
return wb.DocumentCompletedAsync(30000); // Default timeout 30 sec
}
private static TimeoutRegistration WithTimeout<T>(
TaskCompletionSource<T> tcs, int timeout, Action eventRemove)
{
if (timeout == Timeout.Infinite) return default;
var timer = new System.Windows.Forms.Timer();
timer.Tick += (s, e) =>
{
timer.Enabled = false;
timer = null;
eventRemove();
eventRemove = null;
tcs.SetException(new TimeoutException());
tcs = null;
};
timer.Interval = timeout;
timer.Enabled = true;
return new TimeoutRegistration(() =>
{
if (timer == null) return;
timer.Enabled = false;
// Make everything null to avoid memory leaks
timer = null;
eventRemove = null;
tcs = null;
});
}
private struct TimeoutRegistration
{
private Action _unregister;
public TimeoutRegistration(Action unregister)
{
_unregister = unregister;
}
public void Unregister()
{
if (_unregister == null) return;
_unregister();
_unregister = null;
}
}
}
更新: 作为旁注,我看到您使用 wb.ScriptErrorsSuppressed = true
来抑制脚本错误。您知道 WebBrowser
控件可以 configure the Internet Explorer version 模拟吗?要使控件模拟最新(也是最终)版本的 Internet Explorer,版本 11,请在程序开头添加此代码:
Registry.SetValue(@"HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION",
AppDomain.CurrentDomain.FriendlyName, 11000); // IE11
我用 C# 应用程序开发了一个简单的 ID 检查 windows 表单,通过使用 webbrowser 控件传递到网页并获得回复来检查一组给定的 ID 是否有效,一切正常,它one.Now 花费 40 - 60 秒 20 Ids.one 我想使用 C# 中的高级线程概念来加速相同的过程。
代码运行良好我想提高使用线程的性能。今天任何简单的建议都会有很大帮助
private void button2_Click(object sender, EventArgs e)
{
string url = "https://idscheckingsite.com";
WebBrowser wb = new WebBrowser();
wb.ScriptErrorsSuppressed = true;
wb.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(Final_DocumentCompleted);
wb.Navigate(url);
}
private void Final_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
WebBrowser wbs = sender as WebBrowser;
wbs.Document.GetElementById("pannumber").InnerText = ListsofIds[ids];
wbs.Document.GetElementById("frmType1").SetAttribute("value", "24Q");
HtmlElement btnlink = wbs.Document.GetElementById("clickGo1");
btnlink.InvokeMember("Click");
//string response = wbs.DocumentText;
wbs.DocumentCompleted -= new WebBrowserDocumentCompletedEventHandler(Final_DocumentCompleted);
wbs.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(Final_result);
}
private void Final_result(object sender, WebBrowserDocumentCompletedEventArgs e)
{
WebBrowser wbResult = sender as WebBrowser;
string status = wbResult.Document.GetElementById("status").InnerText;
string name = wbResult.Document.GetElementById("name").InnerText;
wbResult.DocumentCompleted -= new WebBrowserDocumentCompletedEventHandler(Final_result);
wbResult.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(Final_DocumentCompleted);
DataRow dr = dt.NewRow();
dr[0] = PANNumber[ids];
dr[1] = status;
dr[2] = name;
dt.Rows.Add(dr);
++ids;
if (ids < 20)
wbResult.Navigate(vurl);
else
{
dataGridView1.DataSource = dt;
}
}
工作正常,但需要使用先进的 C# 线程概念(如果有的话)将性能提高到最大。
这是我的建议。单击按钮 2 时,将启动许多辅助任务。合理的数字是 4,但您可以尝试不同的数字,直到获得最佳性能。每个工作任务将使用自己的 WebBrowser
控件,并将调用 id 的子集。例如,工作任务 #0 将调用 ID 4、8、12、16 和 20,工作任务 #1 将调用 1、5、9、13 和 17 等。然后将等待所有工作任务完成, 然后可以更新 DataGridView
。不涉及多线程。一切都发生在 UI 线程中。不需要锁定或其他线程同步。
private async void button2_Click(object sender, EventArgs e)
{
string url = "https://idscheckingsite.com";
const int WORKER_TASKS_COUNT = 4;
var workerTasks = new Task[WORKER_TASKS_COUNT];
for (int i = 0; i < WORKER_TASKS_COUNT; i++)
{
workerTasks[i] = DoWorkAsync(i);
}
await Task.WhenAll(workerTasks);
dataGridView1.DataSource = dt;
async Task DoWorkAsync(int workerIndex)
{
using (var wb = new WebBrowser())
{
wb.ScriptErrorsSuppressed = true;
for (int i = 0; i < ListsofIds.Length; i++)
{
if (i % WORKER_TASKS_COUNT != workerIndex) continue;
wb.Navigate(url);
await wb; // await for the next DocumentCompleted
wb.Document.GetElementById("pannumber").InnerText = ListsofIds[i];
wb.Document.GetElementById("frmType1").SetAttribute("value", "24Q");
HtmlElement btnlink = wb.Document.GetElementById("clickGo1");
btnlink.InvokeMember("Click");
await wb; // await for the next DocumentCompleted
string status = wb.Document.GetElementById("status").InnerText;
string name = wb.Document.GetElementById("name").InnerText;
DataRow dr = dt.NewRow();
dr[0] = PANNumber[i];
dr[1] = status;
dr[2] = name;
dt.Rows.Add(dr);
}
}
}
}
上面的代码使用了一种有趣的技术来简化 WebBrowser
控件的导航。它不是手动订阅和取消订阅 DocumentCompleted
事件,而是通过 await
ing WebBrowser
控件自动执行。通常这是不可能的,但我们可以通过创建一个 returns a TaskAwaiter
:
public static class WebBrowserExtensions
{
public static TaskAwaiter<Uri> GetAwaiter(this WebBrowser wb)
{
var tcs = new TaskCompletionSource<Uri>();
WebBrowserDocumentCompletedEventHandler handler = null;
handler = (_, e) =>
{
wb.DocumentCompleted -= handler;
tcs.TrySetResult(e.Url);
};
wb.DocumentCompleted += handler;
return tcs.Task.GetAwaiter();
}
}
更新: 在自己使用我的代码后,我发现 await wb
有点混乱,因为 WebBrowser
控件有很多事件可以等待.因此,我通过创建事件的异步版本(而不是等待程序)使其更加明确和可扩展:
public static class WebBrowserExtensions
{
public static Task<Uri> DocumentCompletedAsync(this WebBrowser wb)
{
var tcs = new TaskCompletionSource<Uri>();
WebBrowserDocumentCompletedEventHandler handler = null;
handler = (_, e) =>
{
wb.DocumentCompleted -= handler;
tcs.TrySetResult(e.Url);
};
wb.DocumentCompleted += handler;
return tcs.Task;
}
}
可以这样使用:
await wb.DocumentCompletedAsync();
然后创建更多扩展方法变得微不足道,例如 NavigatedAsync
或 DocumentTitleChangedAsync
。
更新: 无休止地等待不是很好,因此可以在等待的扩展方法中添加超时(以毫秒表示)作为参数。由于整个代码旨在 运行 专门用于 UI 线程,因此我使用了 System.Windows.Forms.Timer
,尽管通常 CancellationToken
可能更方便。该代码有点涉及避免内存泄漏,对于打算 运行 数小时并执行数千个 Web 请求的应用程序来说,这可能是一个问题。
public static class WebBrowserExtensions
{
public static Task<Uri> DocumentCompletedAsync(this WebBrowser wb, int timeout)
{
var tcs = new TaskCompletionSource<Uri>();
WebBrowserDocumentCompletedEventHandler handler = null;
var timeoutRegistration = WithTimeout(tcs, timeout,
() => wb.DocumentCompleted -= handler);
handler = (_, e) =>
{
wb.DocumentCompleted -= handler;
timeoutRegistration.Unregister();
tcs.TrySetResult(e.Url);
};
wb.DocumentCompleted += handler;
return tcs.Task;
}
public static Task<Uri> DocumentCompletedAsync(this WebBrowser wb)
{
return wb.DocumentCompletedAsync(30000); // Default timeout 30 sec
}
private static TimeoutRegistration WithTimeout<T>(
TaskCompletionSource<T> tcs, int timeout, Action eventRemove)
{
if (timeout == Timeout.Infinite) return default;
var timer = new System.Windows.Forms.Timer();
timer.Tick += (s, e) =>
{
timer.Enabled = false;
timer = null;
eventRemove();
eventRemove = null;
tcs.SetException(new TimeoutException());
tcs = null;
};
timer.Interval = timeout;
timer.Enabled = true;
return new TimeoutRegistration(() =>
{
if (timer == null) return;
timer.Enabled = false;
// Make everything null to avoid memory leaks
timer = null;
eventRemove = null;
tcs = null;
});
}
private struct TimeoutRegistration
{
private Action _unregister;
public TimeoutRegistration(Action unregister)
{
_unregister = unregister;
}
public void Unregister()
{
if (_unregister == null) return;
_unregister();
_unregister = null;
}
}
}
更新: 作为旁注,我看到您使用 wb.ScriptErrorsSuppressed = true
来抑制脚本错误。您知道 WebBrowser
控件可以 configure the Internet Explorer version 模拟吗?要使控件模拟最新(也是最终)版本的 Internet Explorer,版本 11,请在程序开头添加此代码:
Registry.SetValue(@"HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION",
AppDomain.CurrentDomain.FriendlyName, 11000); // IE11