C# 什么是警告线程停止运行的正确步骤';他在做什么,在离开前返回?

C# 什么是警告线程停止运行的正确步骤';他在做什么,在离开前返回?,c#,.net,multithreading,winforms,C#,.net,Multithreading,Winforms,在退出应用程序之前,警告正在运行的线程停止正在执行的操作并返回的正确过程是什么 protected Thread T; protected static ManualResetEvent mre = new ManualResetEvent(false); protected static bool ThreadRunning = true; public Form1() { InitializeComponent(); T = new Thread(ThreadFunc);

在退出应用程序之前,警告正在运行的线程停止正在执行的操作并返回的正确过程是什么

protected Thread T;
protected static ManualResetEvent mre = new ManualResetEvent(false);
protected static bool ThreadRunning = true;

public Form1()
{
    InitializeComponent();
    T = new Thread(ThreadFunc);
    T.Start();
}

private void ThreadFunc()
{
    while (ThreadRunning)
    {
        // Do stuff
        Thread.Sleep(40);
    }
    mre.Set();
}

private void ExitButton_Click(object sender, EventArgs e)
{
    ThreadRunning = false;
    mre.WaitOne();
    mre.Close();
    Application.Exit();
}
最初,我的代码设置与上面一样。我对如何正确退出的思考如下:

  • 设置
    ThreadRunning=false
    ,以便下次线程T检查该变量时知道停止
  • 调用
    mre.WaitOne()
    ,等待线程T通过调用
    mre.Set()
    表示它实际上已经完成了
  • 如果是,则取消阻止并继续,处置mre(
    mre.Close()
    )并退出
  • 由于某些原因,在单击“退出”按钮后,上述设置有时会失败,并且整个表单变为非活动状态

    我的新设置如下,但对我来说似乎并不完全正确,例如
    mre.Set()
    不会等待任何东西,而
    Application.Exit()
    紧跟其后。我只是在等待它像以前一样失败,但到目前为止还没有

    protected Thread T;
    protected static ManualResetEvent mre = new ManualResetEvent(false);
    protected static bool ThreadRunning = true;
    
    public Form1()
    {
        InitializeComponent();
        T = new Thread(ThreadFunc);
        T.Start();
    }
    
    private void ThreadFunc()
    {
        while (ThreadRunning)
        {
            // Do stuff
            Thread.Sleep(40);
        }
        mre.WaitOne();
        mre.Close();
    }
    
    private void ExitButton_Click(object sender, EventArgs e)
    {
        ThreadRunning = false;
        mre.Set();
        Application.Exit();
    }
    

    使用
    Thread.IsAlive
    可以使用相同的条件变量模式,如下所示:

    using System;
    using System.Windows.Forms;
    using System.Threading;
    
    namespace WindowsFormsAppTest
    {
    
      public partial class FormTest : Form
      {
    
        static volatile protected bool CancelRequired = false;
        protected Thread TheThread;
    

    使用
    Thread.IsAlive
    可以使用相同的条件变量模式,如下所示:

    using System;
    using System.Windows.Forms;
    using System.Threading;
    
    namespace WindowsFormsAppTest
    {
    
      public partial class FormTest : Form
      {
    
        static volatile protected bool CancelRequired = false;
        protected Thread TheThread;
    

    UI挂起,因为您使用
    mre.WaitOne()阻塞UI线程。
    如果需要等待线程退出,则可以使用其属性并处理应用程序消息,而不需要应用程序事件:

    while(_t.IsAlive)
      Application.DoEvents();
    
    有两种线程取消方法:

    • -线程执行的代码知道它可以被取消,并且可以优雅地处理取消,这就是您在这里尝试做的
    • 命令式-强制线程停止-调用
      线程。中止
      中断
      ,即
    正如@HansPassant所提到的,
    bool
    并不是最好的选择,因为编译器可能会对其进行优化,bool值可以缓存,其更改可能不会通过循环线程来处理。您需要使其至少
    易失性
    ,或者只需重构代码即可使用
    取消源代码

    考虑到你的线程在做什么,也许
    BackgroundWorker
    Timer
    或Producer/Consumer模式是
    线程
    的更好的替代方案,但我没有太多的上下文来推荐任何东西。此外,只有当应用程序中只有一个
    Form1
    实例时,它才能正常工作;如果您有多种形式的应用程序,并且用户可以打开多个
    Form1
    表单,则会出现问题


    一般建议,如果您可以使用实例级字段,请这样做,不要使用静态。

    UI挂起,因为您使用
    mre.WaitOne()阻塞UI线程。
    如果需要等待线程退出,则可以使用其属性并处理应用程序消息,而不需要应用程序事件:

    while(_t.IsAlive)
      Application.DoEvents();
    
    有两种线程取消方法:

    • -线程执行的代码知道它可以被取消,并且可以优雅地处理取消,这就是您在这里尝试做的
    • 命令式-强制线程停止-调用
      线程。中止
      中断
      ,即
    正如@HansPassant所提到的,
    bool
    并不是最好的选择,因为编译器可能会对其进行优化,bool值可以缓存,其更改可能不会通过循环线程来处理。您需要使其至少
    易失性
    ,或者只需重构代码即可使用
    取消源代码

    考虑到你的线程在做什么,也许
    BackgroundWorker
    Timer
    或Producer/Consumer模式是
    线程
    的更好的替代方案,但我没有太多的上下文来推荐任何东西。此外,只有当应用程序中只有一个
    Form1
    实例时,它才能正常工作;如果您有多种形式的应用程序,并且用户可以打开多个
    Form1
    表单,则会出现问题


    一般建议,如果您可以使用实例级字段,请这样做,不要使用静态。

    在两次操作之间等待40毫秒不会产生任何问题,但如果您必须等待5秒或更长时间怎么办?那么在每次等待之间取消将是有问题的,正确的做法是取消等待本身。实际上做这件事相当容易。只需将
    Thread.Sleep(40)
    替换为
    Task.Delay(40,token.Wait()
    ,其中
    token
    是一个

    就我个人而言,我更喜欢使用
    任务
    而不是
    线程
    ,因为它更容易等待而不会阻塞UI。此任务将使用线程池线程延迟运行。缺点是每40毫秒运行一次的东西并不总是在同一个线程中运行,因此我可能需要解决线程安全问题

    class Form1 : Form
    {
        protected readonly CancellationTokenSource _cts;
        protected readonly Task _task;
    
        public Form1()
        {
            InitializeComponent();
            _cts = new CancellationTokenSource();
            _task = Task.Run(TaskFunc);
            this.FormClosing += Form_FormClosing;
        }
    
        private async Task TaskFunc()
        {
            try
            {
                while (true)
                {
                    // Do async stuff here, using _cts.Token if possible
                    // The stuff will run in thread-pool threads
                    await Task.Delay(40, _cts.Token).ConfigureAwait(false);
                }
            }
            catch (OperationCanceledException)
            {
                // Ignore cancellation exception
            }
        }
    
        private void ExitButton_Click(object sender, EventArgs e)
        {
            this.Close();
        }
    
        private async void Form_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (_task == null || _task.IsCompleted) return;
            e.Cancel = true;
            _cts.Cancel();
            this.Visible = false; // Or give feedback that the form is closing
            var completedTask = await Task.WhenAny(_task, Task.Delay(5000));
            if (completedTask != _task) Debug.WriteLine("Task refuses to die");
            _task = null;
            await Task.Yield(); // To ensure that Close won't be called synchronously
            this.Close(); // After await we are back in the UI thread
        }
    }
    

    在两次操作之间等待40毫秒不会产生任何问题,但如果您必须等待5秒或更长时间怎么办?那么在每次等待之间取消将是有问题的,正确的做法是取消等待本身。实际上做这件事相当容易。只需将
    Thread.Sleep(40)
    替换为
    Task.Delay(40,token.Wait()
    ,其中
    token
    是一个

    就我个人而言,我更喜欢使用
    任务
    而不是
    线程
    ,因为它更容易等待而不会阻塞UI。此任务将使用线程池线程延迟运行。缺点是每40毫秒运行一次的东西并不总是在同一个线程中运行,因此我可能需要解决线程安全问题

    class Form1 : Form
    {
        protected readonly CancellationTokenSource _cts;
        protected readonly Task _task;
    
        public Form1()
        {
            InitializeComponent();
            _cts = new CancellationTokenSource();
            _task = Task.Run(TaskFunc);
            this.FormClosing += Form_FormClosing;
        }
    
        private async Task TaskFunc()
        {
            try
            {
                while (true)
                {
                    // Do async stuff here, using _cts.Token if possible
                    // The stuff will run in thread-pool threads
                    await Task.Delay(40, _cts.Token).ConfigureAwait(false);
                }
            }
            catch (OperationCanceledException)
            {
                // Ignore cancellation exception
            }
        }
    
        private void ExitButton_Click(object sender, EventArgs e)
        {
            this.Close();
        }
    
        private async void Form_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (_task == null || _task.IsCompleted) return;
            e.Cancel = true;
            _cts.Cancel();
            this.Visible = false; // Or give feedback that the form is closing
            var completedTask = await Task.WhenAny(_task, Task.Delay(5000));
            if (completedTask != _task) Debug.WriteLine("Task refuses to die");
            _task = null;
            await Task.Yield(); // To ensure that Close won't be called synchronously
            this.Close(); // After await we are back in the UI thread
        }
    }
    

    new Thread
    用于启动和忘记线程,您不会取消这些线程,也不会从中获取信息,甚至不会关心它们是否正在运行。如果您想获取信息或取消您应该使用的线程,请查看此处。在最后一个回答中,您的问题回答了bool不是线程同步对象,如ManualResetEvent。“某些情况”是应用程序的发布版本,允许
    class Form1 : Form
    {
        protected readonly CancellationTokenSource _cts;
        protected readonly Thread _thread;
    
        public Form1()
        {
            InitializeComponent();
            _cts = new CancellationTokenSource();
            _thread = new Thread(ThreadFunc);
            _thread.Start();
        }
    
        private void ThreadFunc()
        {
            try
            {
                while (true)
                {
                    // Do stuff here
                    Task.Delay(40, _cts.Token).GetAwaiter().GetResult();
                }
            }
            catch (OperationCanceledException)
            {
                // Ignore cancellation exception
            }
        }
    
        private void ExitButton_Click(object sender, EventArgs e)
        {
            _cts.Cancel();
            this.Visible = false; // Hide the form before blocking the UI
            _thread.Join(5000); // Wait the thread to finish, but no more than 5 sec
            this.Close();
        }
    }
    
    class Form1 : Form
    {
        protected readonly CancellationTokenSource _cts;
        protected readonly Task _task;
    
        public Form1()
        {
            InitializeComponent();
            _cts = new CancellationTokenSource();
            _task = Task.Run(TaskFunc);
            this.FormClosing += Form_FormClosing;
        }
    
        private async Task TaskFunc()
        {
            try
            {
                while (true)
                {
                    // Do async stuff here, using _cts.Token if possible
                    // The stuff will run in thread-pool threads
                    await Task.Delay(40, _cts.Token).ConfigureAwait(false);
                }
            }
            catch (OperationCanceledException)
            {
                // Ignore cancellation exception
            }
        }
    
        private void ExitButton_Click(object sender, EventArgs e)
        {
            this.Close();
        }
    
        private async void Form_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (_task == null || _task.IsCompleted) return;
            e.Cancel = true;
            _cts.Cancel();
            this.Visible = false; // Or give feedback that the form is closing
            var completedTask = await Task.WhenAny(_task, Task.Delay(5000));
            if (completedTask != _task) Debug.WriteLine("Task refuses to die");
            _task = null;
            await Task.Yield(); // To ensure that Close won't be called synchronously
            this.Close(); // After await we are back in the UI thread
        }
    }