简单线程,为什么会发生这种情况?(C#WinForms)

简单线程,为什么会发生这种情况?(C#WinForms),c#,multithreading,C#,Multithreading,我目前正在探索C#WinForms中的线程实现,我创建了一个简单的应用程序: 我只是想知道,为什么在我启动、停止、启动和再次停止应用程序之后,这个应用程序的内存使用量一直在增长。我认为当我按下停止按钮时,我的线程实例不会真正终止或中止。请考虑下面的代码,非常感谢您的帮助或建议。p> using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System

我目前正在探索C#WinForms中的线程实现,我创建了一个简单的应用程序:

我只是想知道,为什么在我启动、停止、启动和再次停止应用程序之后,这个应用程序的内存使用量一直在增长。我认为当我按下停止按钮时,我的线程实例不会真正终止或中止。请考虑下面的代码,非常感谢您的帮助或建议。p>
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace ThreadingTest
{
    public partial class Form1 : Form
    {
        private delegate void TickerDelegate(string s);
        bool stopThread = false;
        TickerDelegate tickerDelegate1;
        Thread thread1;

        public Form1()
        {
            InitializeComponent();
            tickerDelegate1 = new TickerDelegate(SetLeftTicker);
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            thread1 = new Thread(new ThreadStart(disp));
            thread1.Start();
        }

        void disp()
        {
            while (stopThread == false)
            {
                listBox1.Invoke(tickerDelegate1, new object[] { DateTime.Now.ToString() });
                Thread.Sleep(1000);
            }
        }

        private void SetLeftTicker(string s)
        {
            listBox1.Items.Add(s);
        }

        private void btnStop_Click(object sender, EventArgs e)
        {
            stopThread = true;
            if (thread1.IsAlive)
            {
                thread1.Abort();
            }
        }

        private void btnStart_Click(object sender, EventArgs e)
        {
            stopThread = false;
            thread1 = new Thread(new ThreadStart(disp));
            thread1.Start();
        }

        private void btnCheck_Click(object sender, EventArgs e)
        {
            if (thread1.IsAlive)
            {
                MessageBox.Show("Is Alive!");
            }
        }

        private void btnClear_Click(object sender, EventArgs e)
        {
            listBox1.Items.Clear();
        }
    }
}


在btnStop_Click中,您将标志设置为true,然后立即检查它是否仍处于活动状态。这不会给线程一个自然终止的机会。相反,您应该使用Join(正如hydrogen所建议的)在线程上执行等待,如果它超时(出于任何原因),则中止该线程。

新线程在内存方面非常昂贵。我认为每个新线程的默认堆栈大小是1MB。其他人提到,在线程上调用Abort并不是结束线程的好方法(如果线程阻塞,它甚至可能不会中止线程)。但是,在您的情况下,我不认为Abort是内存增长的原因

volatile bool stopThread = false;

基本上,.NET垃圾收集器可能只是没有腾出时间来释放它分配的内存。您可以尝试使用GC.Collect()强制收集,但不应在生产代码中执行此操作。

好的,有几点建议:

使用易失性标志 让你的旗帜不稳定。。。如果没有,那么线程可能永远看不到对标志的更新

volatile bool stopThread = false;
设为背景 将
IsBackground
属性设置为true:如果应用程序退出,它将强制终止线程,否则即使在应用程序关闭后,您也可能会得到一个“重影线程”

thread1.IsBackground = true;
thread1.Start();
如果线程刚刚开始休眠,那么您将在它有机会读取标志之前中止它。。。此外,您不想使用
中止
,因为:

…如果一个线程调用另一个线程的中止 线程,中止中断任何 代码正在运行。这是一个机会 线程可能会中止,而 块正在运行,在这种情况下 最后,块被中止。有 还有一个机会,一个静态 构造函数可能被中止。难得 在某些情况下,这可能会阻止 该类不会在该类中创建 应用程序域

使用中断而不是中止 因此,我建议您调用
Interrupt
并在线程内处理异常,而不是使用abort:

private void btnStop_Click(object sender, EventArgs e)
{
    // have another method for re-use
    StopThread();
}

private void StopThread()
{
    stopThread = true;

    // the time out is 100 ms longer than the thread sleep
    thread1.Join(1100);

    // if the thread is still alive, then interrupt it
    if(thread1.IsAlive)
    {
        thread1.Interrupt();
    }
}
不要“泄漏”螺纹 每次单击“开始”按钮都会泄漏线程。。。如果已为
thread1
分配了一个线程,并且您为其分配了另一个线程,则前一个线程将继续存在。在启动另一个线程之前,需要停止上一个线程

private void btnStart_Click(object sender, EventArgs e)
{
    // stop the previous thread
    StopThread();

    // create a new thread
    stopThread = false;
    thread1 = new Thread(new ThreadStart(disp));
    thread1.IsBackground = true;// set it to background again
    thread1.Start();
}
处理中断 最后,您需要处理线程中的中断:

void disp()
{
    try
    {
        while (stopThread == false)
        {
            listBox1.Invoke(tickerDelegate1, new object[] { DateTime.Now.ToString() });
            Thread.Sleep(1000);
        }
    }
    catch(ThreadInterruptedException)
    {
        // ignore the exception since the thread should terminate
    }
}

我想就是这样。。。哦事实上,还有一件事:小心穿线;)

首先,您应该重命名form1和按钮1-4,使其具有有意义的名称;在这种情况下,您很幸运,通过阅读事件代码,可以很明显地看出哪些按钮对应于哪些数字。第二,尽管我知道这可能只是尝试线程,但正确的方法是使用计时器,而不是休眠一秒钟的线程。谢谢,我会接受你的建议。我更喜欢在线程上设置超时执行
.Join()
,而不是执行
.Abort()
。如果超时过期,您可以将
.Abort()
作为最后手段。如果您需要在长时间
.Sleep()
期间中断它,那么您可以执行
.interrupt()
操作,但请确保捕获
线程中断
异常。@hydrogen:这个可以解决我的问题吗?嗯。。。如果你不介意的话,先生,你能举个例子吗?我试过GC.Collect(),看起来不错。应用程序的内存使用似乎不再增长。但正如你所说,这是解决问题的一种方法,而不是真正的答案。答案是没有问题。NET的垃圾收集器是由内存需求触发的。换句话说,在进程中看到未使用的内存挂起是完全正常的(也是可以接受的)。当需要内存时,它将被释放。好的,先生,非常感谢您的回答。。。我会试试你的代码。如果你一直按start,OP的代码会泄漏线程,但他说他正在执行start、stop、start、stop等操作,所以这不能解释内存增长的原因。我认为这只是正常的GC行为,因为当他使用GC.Collect时,内存被回收。但是还是有好的建议。@Josh,我不知道为什么内存在增长,你对GC行为的猜测可能是正确的。。。“开始”按钮的问题实际上是对线程可能泄漏的另一个领域的评论。谢谢你指出这一点!