【问题标题】:How to end a endless looping background thread如何结束无限循环的后台线程
【发布时间】:2020-05-20 10:56:29
【问题描述】:

专家,

在 C# 中,我需要在主 (GUI) 线程旁边做一些无休止的循环工作。因此,主线程创建循环线程,当主线程到达某个点时,它应该结束该循环。这是我知道是错误的代码:

private bool m_MayRun;
private Thread m_Thread;

void mainthread() //e.g. the Form_Load event
{
    ...
    m_Thread = new Thread(loopingThread);
    m_Thread.Start();
    ...
    m_MayRun = false; // e.g. later in the Form_Closing event
    m_Thread.Join();
}

void loopingThread()
{
    m_MayRun = true;
    while(m_MayRun) // loop until m_MayRun is set to 'false' from the main thread
    {
        DoStuff();
    }
}

明显的错误是使用变量“m_MayRun”有时会导致阻塞主线程,尤其是“加入”命令。在 C++ 中我会声明它是原子的,但我在 C# 中找不到这样的东西。这个 m_MayRun 变量是唯一一个必须从主线程访问的变量,所以我正在寻找一种简单而美观的方法(没有很大的开销)来解决这个同步问题。谢谢

【问题讨论】:

  • 如何将CancellationToken 传递给您的loopingThread 方法,如果IsCancellationRequested 为真,则检查每个循环,如果是,则从循环中检查break。现在您可以私下存储CancellationTokenSource 并在需要时调用Cancel (ms docs on cancelling managed threads)
  • 我认为主线程有时会因为thread.Join()方法和while loop DoStuff()内部执行的作业而被阻塞。你真的需要等到后台线程结束(例如,在它完成后你使用它的计算结果)吗?或者你只是想完成后台线程而不需要等到它完成?
  • 我想我不会在这里过度设计。只需将m_MayRun 设置为易失性,不要将其设置为true inside 线程,但在开始之前。虽然不是特别有可能,但在主线程到达Join 之前,线程实际上并没有启动。在这种情况下,您将拥有一个无限运行的线程和一个阻塞的事件队列。
  • 你真的在循环中不停地DoStuff吗?一个DoStuff和下一个之间没有延迟?
  • @Iliar Turdeshev:我只需要以适当的方式取消 endles 循环即可进行清理。我知道“中止”命令,但这意味着我必须在线程中使用 try/catch/finally 构造,我不喜欢这有几个原因。

标签: c# multithreading synchronization


【解决方案1】:

按照其他人的建议使用 volatile 关键字是一个好的开始。 volatile 关键字基本上告诉编译器该变量将从不同的线程访问,这将导致编译器减少/禁用所述变量的缓存。线程可能在使用不同缓存的不同内核/处理器上运行,这些缓存存储不同版本的变量。

如果您想确保线程不会同时访问变量,您可以使用锁。锁将导致您的线程等待直到锁空闲。通常,您会在类中创建一个仅为该目的而保留的对象。使用 locks 和 volatile 关键字,您的代码将如下所示:

private volatile bool m_MayRun;
private Thread m_Thread;
private readonly object _lock = new object();

void mainthread() //e.g. the Form_Load event
{
    ...
    m_Thread = new Thread(loopingThread);
    m_Thread.Start();
    ...

    lock(_lock)
        m_MayRun = false; // e.g. later in the Form_Closing event

    m_Thread.Join();
}

void loopingThread()
{
    lock(_lock)
        m_MayRun = true;

    while(true) 
    {
        lock(_lock)
        {
            if(m_maxRun)
                break;
        }
        DoStuff();            
    }
}

编辑: 正如@Theodor Zoulias 指出的那样,您不必将 volatile 关键字与锁定一起使用。实际上,在这种特定情况下,您可以使用 volatile OR 锁或互锁。要了解每种方法的好处,您可能需要查看Volatile vs. Interlocked vs. lock

【讨论】:

  • 如果您在每次访问m_MayRun 字段时使用lock,则无需使用volatile
【解决方案2】:

开销最低的方法可能是使用interlocked 类中的方法。更典型的 c# 方法是使用Cancellation token 创建一个task。第三个选项是manualResetEvent(Slim)。还有 volatile -keyword 在这种情况下似乎应该解决同步问题。

【讨论】:

    猜你喜欢
    • 2020-04-21
    • 2013-12-06
    • 2013-09-30
    • 2014-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多