我写了一个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次之后)。当它卡住时,我看到的最后一个日志是
浏览网页浏览器。
我认为这与终止我的线程有关。有人可以告诉我如何运行运行DocumentCompleted
事件的线程吗?
答案 0 :(得分:2)
我没有看到任何明显的死锁原因,在测试代码时也没有重现。代码中存在许多缺陷,但没有任何东西在这里大喊大叫!"高声。我只能提出建议:
请考虑您根本不需要线程。在等待浏览器代码完成作业时,while (th.IsAlive) { }
热循环阻塞主线程。这不是一个使用线程的有用方法,你也可以使用你的主线程。这可以立即消除大量潜在的悬挂原因。
PageLoaded中的状态逻辑存在风险。我们看不到所有这一切,但一个明显的问题是你将WebBrowser两次处理掉了。如果您在没有Navigate()调用的情况下使用return
,那么您将按照描述进行挂起。没有必要取消订阅活动但是同样的故事,如果您取消订阅但不是所有Application.Exit(),那么您将按照描述挂起。状态机很难调试,需要彻底的日志记录。通过移动Dispose()调用并将事件从逻辑中取消订阅来最小化风险,它不属于那里。并且您需要测试当任何Navigate()调用失败时会发生什么,重定向到您不期望的页面。
_wb.Dispose()
电话有风险。请注意,在DocumentCompleted事件处于运行状态时会销毁WebBrowser。从技术上讲,可以将代码执行返回到不再存在或存在的代码。这可以在浏览器中绊倒竞争条件。除了在调试器中,还有一个专用的MDA来检查这个问题。通过在Application.Run()调用它所属的位置后移动Dispose()调用,可以避免这种情况。
while循环会烧掉100%核心,可能会使工作线程匮乏。解释死锁的理由不够,但肯定没必要。请改用Thread.Join()。
您在此代码中创建了许多WebBrowser对象。这是一个非常重的对象,您可以想象,您需要密切关注程序中的内存使用情况。特别是不受管理的那种。如果浏览器泄漏,就像他们经常那样,你可以在技术上创建一个方案,其中WB初始化正常,但没有足够的内存来加载页面。非常赞成仅使用一个 WB。
您需要考虑到这可能是一个环境问题。在该列表的顶部是永远的反恶意软件和防火墙,他们总是有一个非常好的理由专门对待浏览器,因为这是最常见的恶意软件注入向量。您需要在禁用反恶意软件和防火墙的情况下运行测试,以确保它不是导致挂起的原因。
另一个环境问题是我在测试此代码时注意到的问题,谷歌对我经常打击它并开始扼杀请求感到闷闷不乐,大大减慢了代码。与网站所有者交谈并询问他是否有类似的阻止或限制措施,大部分都是。当浏览器重定向到错误页面时,您需要测试状态逻辑以验证它是否仍能正常工作。
另一个环境问题是WB在某些情况下会自动显示对话框。这可能会在第三方代码中死锁,很难诊断。您至少应该将WebBrower.ScriptErrorsSuppressed设置为 true ,但要注意您加载的网页中的Javascript代码,该代码本身会创建新窗口或显示警报对话框。使用一个WB是解决方法。
请注意,您的程序只能与Internet连接和网页服务器一样可靠。当然,这不是一个非常好的地方,两者都是你无法触及的,你不会得到很好的例外来帮助你诊断这样的失败。并且考虑到你可能尚未对你的程序进行足够好的测试,以检查它是否能够在这样的失败中存活下来,它不会发生。
相当一个清单,首先关注消除不必要的线程并暂时抑制反恶意软件。这很快,重点关注仅使用one WebBrowser。
答案 1 :(得分:-1)
汉斯,谢谢你,我能用你的一个想法解决这个问题。当你花时间给我一个很长的答案时,我想以同样的方式回应。
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号中所提到的那样。但是我花了几个小时才解决了这个问题。
无论如何,谢谢你的帮助。