【问题标题】:What is the difference between these two methods for pausing/resuming threads?这两种暂停/恢复线程的方法有什么区别?
【发布时间】:2014-08-11 13:04:07
【问题描述】:

我有一个用于从网站提取数据的多线程应用程序。我希望能够从 UI 暂停和恢复多个线程。在网上搜索后,我了解到两种方法可以用来控制(暂停/恢复)我的线程。

  1. 使用Monitor 类。

  2. 使用EventWaitHandleManualResetEvent 类。


我做了什么:

我有一个名为GetHtml 的函数,它只返回网站的html。为了简洁起见,我只是展示了这个函数的小数部分。

public string GetHtml(string url, bool isProxy = false)
{
    string result = "";
    ExecutionGateway();
    //->> EXTRA CODE FOR FETCHING HTML
    return result;
}

我有一个函数ControlTasks 用于从UI 控制线程,下面我已经解释了ControlTasks 函数使用Monitor 类和EventWaitHandle 类的线程控制方法(我还将简要介绍解释函数ExecutionGateway的工作原理。

1.使用Monitor

private object taskStopper = new object();
public bool ControlTasks(bool isPause)
{
    try
    {
        if (isPause)
        {
            Monitor.Enter(taskStopper);
        }
        else
        {
            Monitor.Exit(taskStopper);
        }
        return true;
    }
    catch (Exception ex)
    {
        Logger.Instance.WriteLog("ControlTasks:", ex, Logger.LogTypes.Error);
        return false;
    }
}

ControlTasks 从 UI 调用,如果 isPause 为真,则在对象 taskStopper 上使用排他锁,否则释放锁,现在是函数 ExecutionGateway,用于获取对象 @ 上的锁987654337@ 但它什么也不做,如下面的代码所示。

private void ExecutionGateway()
{
    lock(taskStopper){  }
}

这样,当ControlTasks 中的isPause 为真时,所有正在运行的线程都进入等待状态,因为taskStopper 被独占锁定,如果isPause 为假,所有线程都将恢复其处理。

2。使用EventWaitHandle

private EventWaitHandle handle = new ManualResetEvent(true);
public bool ControlTasks(bool isPause)
{
    try
    {
        if (isPause)
        {
            handle.Reset();
        }
        else
        {
            handle.Set();
        }
        return true;
    }
    catch (Exception ex)
    {
        Logger.Instance.WriteLog("ControlTasks:", ex, Logger.LogTypes.Error);
        return false;
    }
}

此代码也基本上完成相同的工作,其中事件状态是有信号/无信号的,具体取决于isPause 参数。现在,对应的ExecutionGateway 方法。

private void ExecutionGateway()
{
    handle.WaitOne(Timeout.Infinite);
}

问题:

  1. 这两种方法有什么区别,一种比另一种更好吗?有没有其他方法可以做到这一点?

  2. 我多次面临的主要问题是,如果我使用上述任何一种方法并且我有 100 个线程;当我暂停它们,然后在 5 分钟或更长时间后恢复它们时,UI 开始挂起。用户界面非常无响应。它会更新但一直挂起,并且我在每个时间间隔都不断收到“未响应”消息。我想提到的一件事是每个线程提取数据并通知 UI 通过事件处理获取的数据。这种反应迟钝的原因可能是什么?我的方法有问题吗?

【问题讨论】:

  • 我以前遇到过这种问题;我使用了MonitorSemaphore 的组合。你可能想看看Semaphore
  • 使用方法 1 有一个相当大的缺陷...如果您的后台线程有很多争用,您的 UI 线程可能会在尝试获取锁时被阻止。
  • 问题 2 与您阻塞 UI 线程 真的 长时间有关。当被阻塞时,UI 线程会将消息排队,当它恢复时,这些消息会被处理。一般来说,你应该尽量避免阻塞 UI 线程......
  • @James 但如果线程正在运行,那么它们应该在进入暂停状态之前处理所有 UI 消息,因为线程处于暂停状态,因此它们不会排队任何消息

标签: c# multithreading winforms


【解决方案1】:

我认为使用能够清楚地传达您的意图的结构总是可取的。你想要一个信号给其他线程,他们应该等待(即停止做他们正在做的事情),直到你向他们发出信号他们可以重新开始。您有一个控制线程(您的 UI),并且可能有许多线程在工作并将结果编组回 UI。

方法 1 并不理想,因为锁(至少根据我的经验)最常用于保护不适合在多线程代码中使用的资源。例如,写入共享字段。

方法 2 更有意义,手动重置事件的功能就像一扇门:打开门,东西可以通过,关闭它,它们不能。这正是您要寻找的行为,我认为大多数开发人员很快就会明白这就是您的意图。

至于您的第二个问题,听起来您收到大量消息阻塞 UI。如果您停止所有 100 个线程,然后同时启动它们,那么它们很有可能会非常接近地完成工作,并且都试图将工作结果发送到 UI 线程。为了解决这个问题,您可以尝试在重新启动或使用更少的线程时错开工作。另一种选择是聚合结果并仅每 x 秒调度一次 UI - 但这需要更多的工作。

【讨论】:

    【解决方案2】:

    在选项 1 中,使用Monitor 类意味着一次只有一个线程拥有监视器对象的排他锁。这意味着在您的 100 个线程中,一次只有 1 个正在处理,这违背了使用线程的目的。这也意味着您的 GUI 线程必须等到当前工作线程完成才能获得锁。

    ManualResetEvent 是更好的选择,因为它用于在线程之间发送信号,而不是防止多线程访问。

    我不知道为什么您的 GUI 在使用第二个选项时反应如此迟钝,但我认为这与您的手动重置事件无关。您更有可能遇到 GUI 线程被淹没的不同问题。您建议您有 100 个线程都向 GUI 触发通知事件,这似乎是罪魁祸首。

    如果您调试您的应用程序,并在您的 GUI 无响应时随机中断,会发生什么情况?多次这样做应该可以显示您的 GUI 线程在做什么以及瓶颈在哪里。

    【讨论】:

    • Montior.Enter 仅通过 UI 而不是任何线程使用。所以我认为 UI 是唯一拥有锁的线程,当恢复时它会释放锁。
    • 我做过调试,调试多线程代码有点困难。 . .当我在很长一段时间后恢复线程时,GUI 变得无响应。
    • 你执行网关调用lock,它在内部使用 Monitor.Enter。
    • 是的,但是当 ControlTask​​s 功能已经被 UI 用于 Monitor.Enter 时,为什么当对象已经被 UI 独占锁定时,锁将允许任何单个线程
    • 是的,当 UI 调用Monitor.Enter 时,它将拥有锁,所有其他线程将被lock 阻塞。但是当 UI 释放锁时,一次只有一个工作线程可以获取排他锁。
    猜你喜欢
    • 2013-08-08
    • 1970-01-01
    • 2013-09-08
    • 2019-03-31
    • 2022-11-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多