简体   繁体   English

如何在c#中正确终止工作线程

[英]How to terminate a worker thread correctly in c#

Problem statement 问题陈述

I have a worker thread that basically scans a folder, going into the files within it, and then sleeps for a while. 我有一个工作线程,基本上扫描文件夹,进入其中的文件,然后睡了一会儿。 The scanning operation might take 2-3 seconds but not much more. 扫描操作可能需要2-3秒但不多。 I'm looking for a way to stop this thread elegantly. 我正在寻找一种方法来优雅地阻止这个线程。

Clarification : I want to stop the thread while it's sleeping , and not while it's scanning . 澄清 :我想在线程休眠时停止线程,而不是在扫描时 However, the problem is that I do not know what is the current state of the thread. 但是,问题是我不知道线程的当前状态是什么。 If it's sleeping I want it to exit immediately. 如果它正在睡觉我想让它立即退出。 If it's scanning, I want it to exit the moment it tries to block. 如果正在扫描,我希望它在尝试阻止的那一刻退出。

Attempts at a solution 尝试解决方案

At first I was using Sleep and Interrupt. 起初我正在使用睡眠和中断。 Then I found out that Interrupt doesn't really interrupt the Sleep - it only works when the threads TRIES to go into sleeping. 然后我发现中断并没有真正中断睡眠 - 只有在线程TRIES进入睡眠状态时它才有效。

So I switched to Monitor Wait&Pulse. 所以我切换到Monitor Wait&Pulse。 Then I found out that the Pulse only works when I'm actually in the Wait. 然后我发现Pulse只在我实际上在等待时才有效。 So now I have a thread which looks like that: 所以现在我有一个看起来像这样的线程:

while (m_shouldRun)
{
    try
    {
        DoSomethingThatTakesSeveralSeconds();
        lock (this)
        {
            Monitor.Wait(this, 5000);
        }
    }
    catch (ThreadInterruptedException)
    {
        m_shouldRun = false;
    }
}

And now I need to craft my Stop function. 现在我需要制作我的Stop功能。 So I started with: 所以我开始:

public void Stop()
{
    m_shouldRun = false;
    lock (this)
    {
        Monitor.Pulse(this);
    }
    thread.Join();
}

But this doesn't work because I may be pulsing while the thread works (while it's not waiting). 但是这不起作用,因为我可能在线程工作时发出脉冲(虽然它没有等待)。 So I added Interrupt: 所以我添加了Interrupt:

public void Stop()
{
    m_shouldRun = false;
    thread.Interrupt();
    lock (this)
    {
        Monitor.Pulse(this);
    }
    thread.Join();
}

Another option is to use: 另一种选择是使用:

public void Stop()
{
    m_shouldRun = false;
    while (!thread.Join(1000))
    {
        lock (this)
        {
            Monitor.Pulse(this);
        }
    }
}

The question 这个问题

What is the preferred method? 什么是首选方法? Is there a third method which is preferable? 有第三种方法更可取吗?

The way to stop a thread elegantly is to leave it finish by itself. 优雅地停止线程的方法是让它自己完成。 So inside the worker method you could have a boolean variable which will check whether we want to interrupt. 所以在worker方法中你可以有一个布尔变量来检查我们是否要中断。 By default it will be set to false and when you set it to true from the main thread it will simply stop the scanning operation by breaking from the processing loop. 默认情况下,它会被设置为false ,当你把它设置为true从主线程它只会通过处理环路,打破停止扫描操作。

Another alternative is to use events: 另一种选择是使用事件:

private ManualResetEvent _event = new ManualResetEvent(false);


public void Run() 
{
 while (true)
 {
    DoSomethingThatTakesSeveralSeconds();
    if (_event.WaitOne(timeout))
      break;
 }
}

public void Stop() 
{
   _event.Set();
   thread.Join();
}

I recommend to keep it simple: 我建议保持简单:

while (m_shouldRun)
{
    DoSomethingThatTakesSeveralSeconds();
    for (int i = 0; i < 5; i++)  // example: 5 seconds sleep
    {
        if (!m_shouldRun)
            break;
        Thread.Sleep(1000);
    }
}

public void Stop()
{
    m_shouldRun = false;
    // maybe thread.Join();
}

This has the following advantages: 这具有以下优点:

  • It smells like busy waiting, but it's not. 它闻起来像忙着等待,但事实并非如此。 $NUMBER_OF_SECONDS checks are done during the waiting phase, which is not comparable to the thousands of checks done in real busy waiting. $ NUMBER_OF_SECONDS检查在等待阶段完成,这与在真正繁忙等待中完成的数千次检查无法比较。
  • It's simple, which greatly reduces the risk of error in multi-threaded code. 它很简单,大大降低了多线程代码中出错的风险。 All your Stop method needs to do is to set m_shouldRun to false and (maybe) call Thread.Join (if it is necessary for the thread to finish before Stop is left). 你所有的Stop方法需要做的是将m_shouldRun设置为false并且(可能)调用Thread.Join (如果在Stop之前线程必须完成)。 No synchronization primitives are needed (except for marking m_shouldRun as volatile). 不需要同步原语(除了将m_shouldRun标记为volatile)。

I came up with separately scheduling the task: 我想出了单独安排任务:

using System;
using System.Threading;

namespace ProjectEuler
{
    class Program
    {
        //const double cycleIntervalMilliseconds = 10 * 60 * 1000;
        const double cycleIntervalMilliseconds = 5 * 1000;
        static readonly System.Timers.Timer scanTimer =
            new System.Timers.Timer(cycleIntervalMilliseconds);
        static bool scanningEnabled = true;
        static readonly ManualResetEvent scanFinished =
            new ManualResetEvent(true);

        static void Main(string[] args)
        {
            scanTimer.Elapsed +=
                new System.Timers.ElapsedEventHandler(scanTimer_Elapsed);
            scanTimer.Enabled = true;

            Console.ReadLine();
            scanningEnabled = false;
            scanFinished.WaitOne();
        }

        static void  scanTimer_Elapsed(object sender,
            System.Timers.ElapsedEventArgs e)
        {
            scanFinished.Reset();
            scanTimer.Enabled = false;

            if (scanningEnabled)
            {
                try
                {
                    Console.WriteLine("Processing");
                    Thread.Sleep(5000);
                    Console.WriteLine("Finished");
                }
                finally
                {
                    scanTimer.Enabled = scanningEnabled;
                    scanFinished.Set();
                }
            }
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM