C# 关于在.NET中干净地终止线程的问题

C# 关于在.NET中干净地终止线程的问题,c#,.net,winforms,multithreading,C#,.net,Winforms,Multithreading,我从我读过的大量关于这个主题的文章中了解到Thread.Abort()是邪恶的,所以我目前正在把我的Abort的一部分撕下来,以便以更干净的方式替换它;在比较stackoverflow上的用户策略之后,以及在阅读MSDN中的“之后,这两种方法都非常相似——即使用volatile bool方法检查策略,这很好,但我还有几个问题 在这里,我首先想到的是,如果您没有一个简单的工作进程,它只是运行一个循环的处理代码,该怎么办?例如,对我来说,我的流程是一个后台文件上传流程,事实上,我确实循环了每个文件,

我从我读过的大量关于这个主题的文章中了解到Thread.Abort()是邪恶的,所以我目前正在把我的Abort的一部分撕下来,以便以更干净的方式替换它;在比较stackoverflow上的用户策略之后,以及在阅读MSDN中的之后,这两种方法都非常相似——即使用
volatile bool
方法检查策略,这很好,但我还有几个问题

在这里,我首先想到的是,如果您没有一个简单的工作进程,它只是运行一个循环的处理代码,该怎么办?例如,对我来说,我的流程是一个后台文件上传流程,事实上,我确实循环了每个文件,所以这很重要,我可以在顶部添加我的
,而(!\u shoulldstop)
,它覆盖了我的每个循环迭代,但在它进入下一个循环迭代之前,我有更多的业务流程,我希望这个取消过程是快速的;不要告诉我我需要在我的整个工作函数中每隔4-5行循环一次的时候洒这些东西

我真的希望有更好的方法,请有人告诉我这是否是正确的[而且是唯一的?]方法,或者他们过去为实现我的目标而使用的策略

谢谢大家


进一步阅读:假设工作线程将循环。这对我来说不舒服。如果它是一个线性但及时的后台操作呢?

很不幸,在多线程中,为了保持整洁,您常常不得不牺牲“快速性”。。。如果
中断
线程,可以立即退出该线程,但它不会很干净。因此,不必每隔4-5行执行一次
\u shouldStop
检查,但如果中断了线程,则应处理异常并以干净的方式退出循环

更新 即使它不是一个循环线程(也就是说,它可能是一个执行一些长时间运行的异步操作或输入操作的某种类型的块的线程),您也可以中断它,但您仍然应该捕获
ThreadInterruptedException
并干净地退出线程。我认为你读过的例子非常恰当

更新2.0 是的,我有一个例子。。。我将根据您引用的链接向您展示一个示例:

public class InterruptExample
{
    private Thread t;
    private volatile boolean alive;

    public InterruptExample()
    {
        alive = false;

        t = new Thread(()=>
        {
            try
            {
                while (alive)
                {
                    /* Do work. */
                }
            }
            catch (ThreadInterruptedException exception)
            {
                /* Clean up. */
            }
        });
        t.IsBackground = true;
    }

    public void Start()
    {
        alive = true;
        t.Start();
    }


    public void Kill(int timeout = 0)
    {
        // somebody tells you to stop the thread
        t.Interrupt();

        // Optionally you can block the caller
        // by making them wait until the thread exits.
        // If they leave the default timeout, 
        // then they will not wait at all
        t.Join(timeout);
    }
}

可能问题的一部分是您有这么长的方法/while循环。无论是否存在线程问题,都应该将其分解为更小的处理步骤。让我们假设这些步骤是Alpha()、Bravo()、Charlie()和Delta()

然后,您可以这样做:

    public void MyBigBackgroundTask()
    {
        Action[] tasks = new Action[] { Alpha, Bravo, Charlie, Delta };
        int workStepSize = 0;
        while (!_shouldStop)
        {
            tasks[workStepSize++]();
            workStepSize %= tasks.Length;
        };
    }
因此,是的,它会无休止地循环,但会检查每个业务步骤之间是否该停止

所有这些响应都假设工作线程将循环。那对我来说不舒服

没有很多方法可以让代码花费很长时间。循环是一个非常重要的编程构造。使代码不循环需要很长时间,需要大量语句。数十万


或者调用其他为您执行循环的代码。是的,很难让代码按需停止。这根本不起作用。

你不必在到处循环的时候洒水。外部while循环只是检查它是否被告知停止,如果是,则不进行另一次迭代

如果您有一个直接的“go do something and close out”线程(其中没有循环),那么您只需在线程内的每个主要点之前或之后检查_shouldStop布尔值。这样你就知道它是应该继续下去还是应该纾困

例如:

public void DoWork() {

  RunSomeBigMethod();
  if (_shouldStop){ return; }
  RunSomeOtherBigMethod();
  if (_shouldStop){ return; }
  //....
}


如果取消是您正在构建的东西的一个要求,那么应该像对待代码的其余部分一样尊重它——这可能是您必须设计的东西

让我们假设你的线程一直在做两件事情中的一件

  • CPU受限的东西
  • 等待内核
  • 如果您在讨论的线程中受到CPU的限制,那么您可能有一个插入纾困支票的好位置。如果您正在调用其他人的代码来执行一些长时间运行的CPU限制任务,那么您可能需要修复外部代码,将其移出进程(中止线程是邪恶的,但中止进程是定义良好且安全的),等等

    如果您正在等待内核,那么在等待过程中可能会涉及一个句柄(或fd,或mach端口…)。通常,如果销毁相关句柄,内核将立即返回一些失败代码。如果您使用的是.net/java/etc,那么最终可能会出现异常。在C语言中,无论您已经准备好了什么代码来处理系统调用失败,都会将错误传播到应用程序中有意义的部分。无论采用哪种方式,您都可以相当干净、非常及时地从底层跳出,而不需要到处散布新代码

    我在这类代码中经常使用的一种策略是跟踪需要关闭的句柄列表,然后让我的abort函数设置一个“cancelled”标志,然后关闭它们。当函数失败时,它可以检查标志并报告由于取消而导致的失败,而不是由于特定的异常/错误号

    您似乎在暗示取消的可接受粒度在服务调用级别。这可能不是一个好主意——您最好同步取消后台工作,并从前台线程加入旧的后台线程。这样更干净,因为:

  • 当旧的bgwork线程在意外延迟后恢复生命时,它避免了一类争用条件

  • 它可以隐藏挂起的背景线程的效果,从而避免挂起的背景进程可能导致的隐藏线程/内存泄漏

  • 这有两个原因
    abstract class StateMachine : IDisposable
    {
        public abstract IEnumerable<object> Main();
    
        public virtual void Dispose()
        {
            /// ... override with free-ing code ...
       }
    
       bool wasCancelled;
    
       public bool Cancel()
       {
         // ... set wasCancelled using locking scheme of choice ...
        }
    
       public Thread Run()
       {
           var thread = new Thread(() =>
               {
                  try
                  {
                    if(wasCancelled) return;
                    foreach(var x in Main())
                    {
                        if(wasCancelled) return;
                    }
                  }
                  finally { Dispose(); }
               });
           thread.Start()
       }
    }
    
    class MyStateMachine : StateMachine
    {
       public override IEnumerabl<object> Main()
       {
           DoSomething();
           yield return null;
           DoSomethingElse();
           yield return null;
       }
    }
    
    // then call new MyStateMachine().Run() to run.