终止线程 运行 一个事件
Terminating Thread Running an Event
我写了一个 API 来自动化某个网站。但是,在测试阶段,我注意到(不太确定)我的线程没有被正确终止。
我正在使用 WebBrowser
对象在线程内导航,以便它与我的程序同步工作:
private void NavigateThroughTread(string url)
{
Console.WriteLine("Defining thread...");
var th = new Thread(() =>
{
_wb = new WebBrowser();
_wb.DocumentCompleted += PageLoaded;
_wb.Visible = true;
_wb.Navigate(url);
Console.WriteLine("Web browser navigated.");
Application.Run();
});
Console.WriteLine("Thread defined.");
th.SetApartmentState(ApartmentState.STA);
Console.WriteLine("Before thread start...");
th.Start();
Console.WriteLine("Thread started.");
while (th.IsAlive) { }
Console.WriteLine("Journey ends.");
}
private void PageLoaded(object sender, WebBrowserDocumentCompletedEventArgs e)
{
Console.WriteLine("Pages loads...");
.
.
.
switch (_action)
{
.
.
.
case ENUM.FarmActions.Idle:
_wb.Navigate(new Uri("about:blank"));
_action = ENUM.FarmActions.Exit;
return;
case ENUM.FarmActions.Exit:
Console.WriteLine("Disposing wb...");
_wb.DocumentCompleted -= PageLoaded;
_wb.Dispose();
break;
}
Application.ExitThread(); // Stops the thread
}
我是这样调用这个函数的:
public int Attack(int x, int y, ArmyBuilder army)
{
// instruct to attack the village
_action = ENUM.FarmActions.Attack;
//get the army and coordinates
_army = army;
_enemyCoordinates[X] = x;
_enemyCoordinates[Y] = y;
//Place the attack command
_errorFlag = true; // the action is not complated, the flag will set as false once action is complete
_attackFlag = false; // attack is not made yet
Console.WriteLine("Journey starts");
NavigateThroughTread(_url.GetUrl(ENUM.Screens.RallyPoint));
return _errorFlag ? -1 : CalculateDistance();
}
所以问题是,当我调用 Attack
函数时,像这样调用了几次:
_command.Attack(509, 355, new ArmyBuilder(testArmy_lc));
_command.Attack(509, 354, new ArmyBuilder(testArmy_lc));
_command.Attack(505, 356, new ArmyBuilder(testArmy_lc));
_command.Attack(504, 356, new ArmyBuilder(testArmy_lc));
_command.Attack(504, 359, new ArmyBuilder(testArmy_lc));
_command.Attack(505, 356, new ArmyBuilder(testArmy_lc));
_command.Attack(504, 356, new ArmyBuilder(testArmy_lc));
_command.Attack(504, 359, new ArmyBuilder(testArmy_lc));
我的应用程序大部分时间都卡在其中一个函数中(通常发生在第 4 或第 5 个之后)。当它卡住时,我看到的最后一条日志是
Web browser navigated.
我认为这与我的线程终止有关。有人可以告诉我如何 运行 一个线程 运行 是 DocumentCompleted
事件吗?
我没有看到任何明显的死锁原因,在测试代码时也没有重现。代码中有许多缺陷,但没有什么可以大声喊 "here!" 的。我只能提出建议:
认为您根本不需要线程。 while (th.IsAlive) { }
热循环会在您等待浏览器代码完成作业时阻塞您的主线程。这不是使用线程的有用方法,您还不如使用主线程。这立即消除了大量潜在的挂起原因。
PageLoaded 中的状态逻辑有风险。我们无法看到所有内容,但一个明显的问题是您处理了两次 WebBrowser。如果您在没有调用 Navigate() 的情况下使用 return
,那么您将按照描述挂起。无需取消订阅该事件,但同样的故事,如果您取消订阅但没有全部取消订阅 Application.Exit() 那么您将按照描述挂起。状态机很难调试,彻底的日志记录是必要的。通过移动 Dispose() 调用并取消订阅逻辑之外的事件来最小化风险,它不属于那里。并且您需要测试当任何 Navigate() 调用以失败告终时会发生什么,重定向到您没有预料到的页面。
_wb.Dispose()
调用有风险。请注意,您在 WebBrowser 的 DocumentCompleted 事件运行期间销毁了它。从技术上讲,可以 return 对不再存在或不存在的代码执行代码。这可能会触发浏览器中的竞争条件。除了在调试器中,还有一个专门的 MDA 来检查这个问题。通过将 Dispose() 调用移动到它所属的 Application.Run() 调用之后可以轻松避免这种情况。
while 循环会消耗 100% 的核心,可能会使工作线程饿死。不是解释死锁的充分理由,但肯定是不必要的。请改用 Thread.Join()。
您在这段代码中创建了很多 WebBrowser 对象。它是一个非常重的对象,正如您可以想象的那样,您需要密切关注程序中的内存使用情况。尤其是不受管理的那种。如果浏览器发生泄漏,就像它们经常发生的那样,您可以在技术上创建一个 WB 初始化正常但没有足够的内存来加载页面的场景。强烈赞成只使用 one WB.
您需要考虑这很可能是一个环境问题。在该列表的顶部永远是反恶意软件和防火墙,他们总是有充分的理由特别对待浏览器,因为这是最常见的恶意软件注入向量。您需要 运行 在禁用反恶意软件和防火墙的情况下进行测试,以确保它不是挂起的原因。
另一个环境问题是我在测试这段代码时注意到的,Google 对我如此频繁地点击它感到生气并开始限制请求,大大减慢了代码速度。与网站所有者交谈,询问他是否有类似的阻止或限制对策,大多数人都有。您需要测试您的状态逻辑,以验证它在浏览器重定向到错误页面时是否仍然正常工作。
还有一个环境问题是 WB 在某些情况下会自己显示一个对话框。这可能会在第 3 方代码中出现死锁,很难诊断。您至少应该将 WebBrower.ScriptErrorsSuppressed 设置为 true 但要注意您加载的网页中的 Javascript 代码,它本身会创建新的 windows 或显示警报对话框。使用一个 WB 是解决方法。
请记住,您的程序只能与您的 Internet 连接和网页服务器一样可靠。这当然不是一个非常好的地方,两者都超出您的范围,并且您没有很好的异常来帮助您诊断此类故障。并考虑到您可能还没有足够好地测试您的程序,以检查它是否可以在这样的故障中幸存下来,它发生得还不够。
相当多的清单,首先关注消除不必要的线程并暂时抑制反恶意软件。很快,接下来只关注使用 one WebBrowser.
汉斯,谢谢你,我能够用你的一个想法解决这个问题。当你花时间给我一个长答案时,我想以同样的方式回应。
2 - 我仔细地构建了状态机结构并且有很多日志(你可以从我的git account中看到它)也做了很多调试。我确信在完成导航后,我只使用了一次 Application.ExitThread()
和 wb.Dispose()
。
3 - 我尝试将 wb.Dispose()
放在事件之外,但是我找不到线程仍然存在的任何其他地方。如果我尝试在线程内创建的线程外处理 WebBrowser
,应用程序会给我一个错误。
4 - 我将代码 while (th.IsAlive) { }
更改为 th.Join(2000)
这绝对是一个更好的主意,但没有改变任何东西。它优化了代码,正如你提到的,它防止了我的 CPU.
的 100% 核心烧毁
5 - 我尝试使用在构造函数中实例化的单个 WebBrowser
对象。但是,当我尝试在线程内导航时,应用程序甚至不再触发事件。出于某种原因,我无法使用单个 WB
对象 运行。
6,7 - 我用不同的 PC 和不同的网络(有防火墙和非防火墙保护)测试了我的应用程序。我也更改了 windows 防火墙选项,但没有费力。在我的原始代码中,我确实有 _wb.ScriptErrorsSuppressed = true;
,所以这也不应该是问题所在。
8,9 - 如果是这些原因,我也无能为力。但我怀疑真正的问题是因为他们造成的。
1 - 这个建议很好。我尝试在不使用线程的情况下实现我的代码,它现在工作正常。这是它的样子(仍然需要大量优化)
// Constructer
public FarmActions(string token)
{
// set the urls using the token
_url = new URL(token);
// define web browser properties
_wb = new WebBrowser();
_wb.DocumentCompleted += PageLoaded;
_wb.Visible = true;
_wb.AllowNavigation = true;
_wb.ScriptErrorsSuppressed = true;
}
public int Attack(int x, int y, ArmyBuilder army)
{
// instruct to attack the village
_action = ENUM.FarmActions.Attack;
//get the army and coordinates
_army = army;
_enemyCoordinates[X] = x;
_enemyCoordinates[Y] = y;
//Place the attack command
_errorFlag = true; // the action is not complated, the flag will set as false once action is complete
_attackFlag = false; // attack is not made yet
_isAlive = true;
Console.WriteLine("-------------------------");
Console.WriteLine("Journey starts");
NavigateThroughTread(_url.GetUrl(ENUM.Screens.RallyPoint));
return _errorFlag ? -1 : CalculateDistance();
}
private void NavigateThroughTread(string url)
{
Console.WriteLine("Defining thread...");
_wb.Navigate(url);
while (_isAlive) Application.DoEvents();
}
private void PageLoaded(object sender, WebBrowserDocumentCompletedEventArgs e)
{
Console.WriteLine("Pages loads...");
.
.
.
switch (_action)
{
.
.
.
case ENUM.FarmActions.Idle:
_wb.Navigate(new Uri("about:blank"));
_action = ENUM.FarmActions.Exit;
return;
case ENUM.FarmActions.Exit:
break;
}
_isAlive = false;
}
这就是我在不使用线程的情况下能够等待的方式。
主要问题可能如您在第 3 或第 5 条中提到的那样。但是我花了几个小时无法解决问题。
无论如何,感谢您的帮助。
我写了一个 API 来自动化某个网站。但是,在测试阶段,我注意到(不太确定)我的线程没有被正确终止。
我正在使用 WebBrowser
对象在线程内导航,以便它与我的程序同步工作:
private void NavigateThroughTread(string url)
{
Console.WriteLine("Defining thread...");
var th = new Thread(() =>
{
_wb = new WebBrowser();
_wb.DocumentCompleted += PageLoaded;
_wb.Visible = true;
_wb.Navigate(url);
Console.WriteLine("Web browser navigated.");
Application.Run();
});
Console.WriteLine("Thread defined.");
th.SetApartmentState(ApartmentState.STA);
Console.WriteLine("Before thread start...");
th.Start();
Console.WriteLine("Thread started.");
while (th.IsAlive) { }
Console.WriteLine("Journey ends.");
}
private void PageLoaded(object sender, WebBrowserDocumentCompletedEventArgs e)
{
Console.WriteLine("Pages loads...");
.
.
.
switch (_action)
{
.
.
.
case ENUM.FarmActions.Idle:
_wb.Navigate(new Uri("about:blank"));
_action = ENUM.FarmActions.Exit;
return;
case ENUM.FarmActions.Exit:
Console.WriteLine("Disposing wb...");
_wb.DocumentCompleted -= PageLoaded;
_wb.Dispose();
break;
}
Application.ExitThread(); // Stops the thread
}
我是这样调用这个函数的:
public int Attack(int x, int y, ArmyBuilder army)
{
// instruct to attack the village
_action = ENUM.FarmActions.Attack;
//get the army and coordinates
_army = army;
_enemyCoordinates[X] = x;
_enemyCoordinates[Y] = y;
//Place the attack command
_errorFlag = true; // the action is not complated, the flag will set as false once action is complete
_attackFlag = false; // attack is not made yet
Console.WriteLine("Journey starts");
NavigateThroughTread(_url.GetUrl(ENUM.Screens.RallyPoint));
return _errorFlag ? -1 : CalculateDistance();
}
所以问题是,当我调用 Attack
函数时,像这样调用了几次:
_command.Attack(509, 355, new ArmyBuilder(testArmy_lc));
_command.Attack(509, 354, new ArmyBuilder(testArmy_lc));
_command.Attack(505, 356, new ArmyBuilder(testArmy_lc));
_command.Attack(504, 356, new ArmyBuilder(testArmy_lc));
_command.Attack(504, 359, new ArmyBuilder(testArmy_lc));
_command.Attack(505, 356, new ArmyBuilder(testArmy_lc));
_command.Attack(504, 356, new ArmyBuilder(testArmy_lc));
_command.Attack(504, 359, new ArmyBuilder(testArmy_lc));
我的应用程序大部分时间都卡在其中一个函数中(通常发生在第 4 或第 5 个之后)。当它卡住时,我看到的最后一条日志是
Web browser navigated.
我认为这与我的线程终止有关。有人可以告诉我如何 运行 一个线程 运行 是 DocumentCompleted
事件吗?
我没有看到任何明显的死锁原因,在测试代码时也没有重现。代码中有许多缺陷,但没有什么可以大声喊 "here!" 的。我只能提出建议:
认为您根本不需要线程。
while (th.IsAlive) { }
热循环会在您等待浏览器代码完成作业时阻塞您的主线程。这不是使用线程的有用方法,您还不如使用主线程。这立即消除了大量潜在的挂起原因。PageLoaded 中的状态逻辑有风险。我们无法看到所有内容,但一个明显的问题是您处理了两次 WebBrowser。如果您在没有调用 Navigate() 的情况下使用
return
,那么您将按照描述挂起。无需取消订阅该事件,但同样的故事,如果您取消订阅但没有全部取消订阅 Application.Exit() 那么您将按照描述挂起。状态机很难调试,彻底的日志记录是必要的。通过移动 Dispose() 调用并取消订阅逻辑之外的事件来最小化风险,它不属于那里。并且您需要测试当任何 Navigate() 调用以失败告终时会发生什么,重定向到您没有预料到的页面。_wb.Dispose()
调用有风险。请注意,您在 WebBrowser 的 DocumentCompleted 事件运行期间销毁了它。从技术上讲,可以 return 对不再存在或不存在的代码执行代码。这可能会触发浏览器中的竞争条件。除了在调试器中,还有一个专门的 MDA 来检查这个问题。通过将 Dispose() 调用移动到它所属的 Application.Run() 调用之后可以轻松避免这种情况。while 循环会消耗 100% 的核心,可能会使工作线程饿死。不是解释死锁的充分理由,但肯定是不必要的。请改用 Thread.Join()。
您在这段代码中创建了很多 WebBrowser 对象。它是一个非常重的对象,正如您可以想象的那样,您需要密切关注程序中的内存使用情况。尤其是不受管理的那种。如果浏览器发生泄漏,就像它们经常发生的那样,您可以在技术上创建一个 WB 初始化正常但没有足够的内存来加载页面的场景。强烈赞成只使用 one WB.
您需要考虑这很可能是一个环境问题。在该列表的顶部永远是反恶意软件和防火墙,他们总是有充分的理由特别对待浏览器,因为这是最常见的恶意软件注入向量。您需要 运行 在禁用反恶意软件和防火墙的情况下进行测试,以确保它不是挂起的原因。
另一个环境问题是我在测试这段代码时注意到的,Google 对我如此频繁地点击它感到生气并开始限制请求,大大减慢了代码速度。与网站所有者交谈,询问他是否有类似的阻止或限制对策,大多数人都有。您需要测试您的状态逻辑,以验证它在浏览器重定向到错误页面时是否仍然正常工作。
还有一个环境问题是 WB 在某些情况下会自己显示一个对话框。这可能会在第 3 方代码中出现死锁,很难诊断。您至少应该将 WebBrower.ScriptErrorsSuppressed 设置为 true 但要注意您加载的网页中的 Javascript 代码,它本身会创建新的 windows 或显示警报对话框。使用一个 WB 是解决方法。
请记住,您的程序只能与您的 Internet 连接和网页服务器一样可靠。这当然不是一个非常好的地方,两者都超出您的范围,并且您没有很好的异常来帮助您诊断此类故障。并考虑到您可能还没有足够好地测试您的程序,以检查它是否可以在这样的故障中幸存下来,它发生得还不够。
相当多的清单,首先关注消除不必要的线程并暂时抑制反恶意软件。很快,接下来只关注使用 one WebBrowser.
汉斯,谢谢你,我能够用你的一个想法解决这个问题。当你花时间给我一个长答案时,我想以同样的方式回应。
2 - 我仔细地构建了状态机结构并且有很多日志(你可以从我的git account中看到它)也做了很多调试。我确信在完成导航后,我只使用了一次 Application.ExitThread()
和 wb.Dispose()
。
3 - 我尝试将 wb.Dispose()
放在事件之外,但是我找不到线程仍然存在的任何其他地方。如果我尝试在线程内创建的线程外处理 WebBrowser
,应用程序会给我一个错误。
4 - 我将代码 while (th.IsAlive) { }
更改为 th.Join(2000)
这绝对是一个更好的主意,但没有改变任何东西。它优化了代码,正如你提到的,它防止了我的 CPU.
5 - 我尝试使用在构造函数中实例化的单个 WebBrowser
对象。但是,当我尝试在线程内导航时,应用程序甚至不再触发事件。出于某种原因,我无法使用单个 WB
对象 运行。
6,7 - 我用不同的 PC 和不同的网络(有防火墙和非防火墙保护)测试了我的应用程序。我也更改了 windows 防火墙选项,但没有费力。在我的原始代码中,我确实有 _wb.ScriptErrorsSuppressed = true;
,所以这也不应该是问题所在。
8,9 - 如果是这些原因,我也无能为力。但我怀疑真正的问题是因为他们造成的。
1 - 这个建议很好。我尝试在不使用线程的情况下实现我的代码,它现在工作正常。这是它的样子(仍然需要大量优化)
// Constructer
public FarmActions(string token)
{
// set the urls using the token
_url = new URL(token);
// define web browser properties
_wb = new WebBrowser();
_wb.DocumentCompleted += PageLoaded;
_wb.Visible = true;
_wb.AllowNavigation = true;
_wb.ScriptErrorsSuppressed = true;
}
public int Attack(int x, int y, ArmyBuilder army)
{
// instruct to attack the village
_action = ENUM.FarmActions.Attack;
//get the army and coordinates
_army = army;
_enemyCoordinates[X] = x;
_enemyCoordinates[Y] = y;
//Place the attack command
_errorFlag = true; // the action is not complated, the flag will set as false once action is complete
_attackFlag = false; // attack is not made yet
_isAlive = true;
Console.WriteLine("-------------------------");
Console.WriteLine("Journey starts");
NavigateThroughTread(_url.GetUrl(ENUM.Screens.RallyPoint));
return _errorFlag ? -1 : CalculateDistance();
}
private void NavigateThroughTread(string url)
{
Console.WriteLine("Defining thread...");
_wb.Navigate(url);
while (_isAlive) Application.DoEvents();
}
private void PageLoaded(object sender, WebBrowserDocumentCompletedEventArgs e)
{
Console.WriteLine("Pages loads...");
.
.
.
switch (_action)
{
.
.
.
case ENUM.FarmActions.Idle:
_wb.Navigate(new Uri("about:blank"));
_action = ENUM.FarmActions.Exit;
return;
case ENUM.FarmActions.Exit:
break;
}
_isAlive = false;
}
这就是我在不使用线程的情况下能够等待的方式。
主要问题可能如您在第 3 或第 5 条中提到的那样。但是我花了几个小时无法解决问题。
无论如何,感谢您的帮助。