终止运行事件的线程

时间:2016-04-29 19:53:42

标签: c# multithreading event-handling thread-safety webbrowser-control

我写了一个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事件的线程吗?

2 个答案:

答案 0 :(得分:2)

我没有看到任何明显的死锁原因,在测试代码时也没有重现。代码中存在许多缺陷,但没有任何东西在这里大喊大叫!"高声。我只能提出建议:

  1. 请考虑您根本不需要线程。在等待浏览器代码完成作业时,while (th.IsAlive) { }热循环阻塞主线程。这不是一个使用线程的有用方法,你也可以使用你的主线程。这可以立即消除大量潜在的悬挂原因。

  2. PageLoaded中的状态逻辑存在风险。我们看不到所有这一切,但一个明显的问题是你将WebBrowser两次处理掉了。如果您在没有Navigate()调用的情况下使用return,那么您将按照描述进行挂起。没有必要取消订阅活动但是同样的故事,如果您取消订阅但不是所有Application.Exit(),那么您将按照描述挂起。状态机很难调试,需要彻底的日志记录。通过移动Dispose()调用并将事件从逻辑中取消订阅来最小化风险,它不属于那里。并且您需要测试当任何Navigate()调用失败时会发生什么,重定向到您不期望的页面。

  3. _wb.Dispose()电话有风险。请注意,在DocumentCompleted事件处于运行状态时会销毁WebBrowser。从技术上讲,可以将代码执行返回到不再存在或存在的代码。这可以在浏览器中绊倒竞争条件。除了在调试器中,还有一个专用的MDA来检查这个问题。通过在Application.Run()调用它所属的位置后移动Dispose()调用,可以避免这种情况。

  4. while循环会烧掉100%核心,可能会使工作线程匮乏。解释死锁的理由不够,但肯定没必要。请改用Thread.Join()。

  5. 您在此代码中创建了许多WebBrowser对象。这是一个非常重的对象,您可以想象,您需要密切关注程序中的内存使用情况。特别是不受管理的那种。如果浏览器泄漏,就像他们经常那样,你可以在技术上创建一个方案,其中WB初始化正常,但没有足够的内存来加载页面。非常赞成仅使用一个 WB。

  6. 您需要考虑到这可能是一个环境问题。在该列表的顶部是永远的反恶意软件和防火墙,他们总是有一个非常好的理由专门对待浏览器,因为这是最常见的恶意软件注入向量。您需要在禁用反恶意软件和防火墙的情况下运行测试,以确保它不是导致挂起的原因。

  7. 另一个环境问题是我在测试此代码时注意到的问题,谷歌对我经常打击它并开始扼杀请求感到闷闷不乐,大大减慢了代码。与网站所有者交谈并询问他是否有类似的阻止或限制措施,大部分都是。当浏览器重定向到错误页面时,您需要测试状态逻辑以验证它是否仍能正常工作。

  8. 另一个环境问题是WB在某些情况下会自动显示对话框。这可能会在第三方代码中死锁,很难诊断。您至少应该将WebBrower.ScriptErrorsSuppressed设置为 true ,但要注意您加载的网页中的Javascript代码,该代码本身会创建新窗口或显示警报对话框。使用一个WB是解决方法。

  9. 请注意,您的程序只能与Internet连接和网页服务器一样可靠。当然,这不是一个非常好的地方,两者都是你无法触及的,你不会得到很好的例外来帮助你诊断这样的失败。并且考虑到你可能尚未对你的程序进行足够好的测试,以检查它是否能够在这样的失败中存活下来,它不会发生。

  10. 相当一个清单,首先关注消除不必要的线程并暂时抑制反恶意软件。这很快,重点关注仅使用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号中所提到的那样。但是我花了几个小时才解决了这个问题。

无论如何,谢谢你的帮助。