C# 线程完成任务后释放内存

C# 线程完成任务后释放内存,c#,C#,将断点放在线程启动之前,您会注意到控制台消耗了大约5到8MB的内存,但一旦线程启动,它的内存将达到17到20MB。在关闭控制台之前,此内存将一直使用。在线程完成it任务后,如何释放内存?有更好的解决办法吗 现在的问题是:为什么我需要它,因为垃圾收集器会在需要时自动释放内存。我需要它,因为我正在做网页抓取,我有一个全局类来存储所有抓取的html文本,我必须抓取大约10k页,并将html存储到全局类。发生的事情是:当我在将500个html数据刮到global class后运行这个应用程序时,它几乎吃

将断点放在
线程
启动之前,您会注意到控制台消耗了大约5到8MB的内存,但一旦
线程
启动,它的内存将达到17到20MB。在关闭控制台之前,此内存将一直使用。在
线程
完成it任务后,如何释放内存?有更好的解决办法吗

现在的问题是:为什么我需要它,因为垃圾收集器会在需要时自动释放内存。我需要它,因为我正在做网页抓取,我有一个全局类来存储所有抓取的html文本,我必须抓取大约10k页,并将html存储到全局类。发生的事情是:当我在将500个html数据刮到global class后运行这个应用程序时,它几乎吃掉了我电脑内存的100%,即20GB。所以我需要释放内存。我不能关闭控制台应用程序来释放内存bcoz我有一些计算后,收集所有的html

class DemoData
    {
        public int Id { get; set; }
        public string Text { get; set; }
        public static List<DemoData> data = new List<DemoData>();
    }

    class Program
    {
        

        public static void Main()
        {

            for (var i = 0; i < 5000; i++)
            {
                DemoData.data.Add(new DemoData
                {
                    Id = i,
                    Text = "something....",
                });
                
            }

            foreach (var item in DemoData.data)
            {
                var t = new Thread(new ThreadStart(DoSomething));//put break point here and see.
                t.Name = item.Id.ToString(); ;
                t.Start();
            }

            
            Console.WriteLine("wait");
            Console.ReadLine();


        }

     
        public static void DoSomething()
        {
            Thread thr = Thread.CurrentThread;
            Console.WriteLine(thr.Name);
            
        }


    }
类解调器 { 公共int Id{get;set;} 公共字符串文本{get;set;} 公共静态列表数据=新列表(); } 班级计划 { 公共静态void Main() { 对于(变量i=0;i<5000;i++) { 解调数据。数据。添加(新解调数据 { Id=i, Text=“某物…”, }); } foreach(DemoData.data中的变量项) { var t=new Thread(new ThreadStart(DoSomething));//将断点放在这里并查看。 t、 Name=item.Id.ToString(); t、 Start(); } 控制台。写入线(“等待”); Console.ReadLine(); } 公共静态无效剂量测定法() { Thread thr=Thread.CurrentThread; 控制台写入线(名称); } }
您只需等待垃圾收集器运行,您当前的应用程序不够复杂,不需要运行垃圾收集器,除非关闭,否则您不会看到这种情况发生

因此,框架会在感觉需要时触发GC,然后查看调用堆栈,确定不再需要的对象并删除它们,释放内存,这将完全自动发生,而无需执行任何操作

如果你想帮助GC解决问题,你可以检查你的变量范围,这样GC就可以看到它不再需要了,因为它完全超出了范围,所以避免全局变量,不在不需要的数据对象之间创建链接,在值和引用类型之间做出正确的选择,等等

但是,如果您最终需要手动启动GC,则可以调用GC.Collect() 看见

您可能需要使用内存探查器来检查潜在的内存泄漏

问题的一个可能原因是线程数量太多,没有理由使用比cpu内核更多的线程。使用的每个线程都需要一些内存来进行堆栈和其他内部管理


一个相当简单的方法是将需要访问的地址放入集合中,并使用。这将尝试调整用于最大化吞吐量的线程数。更复杂的变体可以使用一个并发集合和多个使用者。还有许多IO调用的异步变体,以避免在等待IO时阻塞线程的内存开销。我建议您阅读一些多生产者/消费者模式的示例以了解更多详细信息。

如果您的问题是将数据馈送到线程中进行处理,那么我建议您看看这一点,它允许您创建线程安全的处理缓冲区

这里是一个工作示例,注意这是使用.NET5语法

using System;
using System.Threading.Channels;
using System.Net.Http;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;

var sites = Enumerable.Range(0, 5000).Select(i => @"http:\\www.example.com");

//create thread safe buffer no limit on size
var sitebuffer = Channel.CreateUnbounded<string>();
//create thread safe buffer limited to 10 elements
var htmlbuffer = Channel.CreateBounded<string>(10);

async Task Feed()
{
    //while the buffer hasn't closed, wait for new data to be available
    while(await sitebuffer.Reader.WaitToReadAsync())
    {
        //read the next available url from the buffer
        var uri = await sitebuffer.Reader.ReadAsync();
        var http = new HttpClient();
        var html= await http.GetAsync(uri);
        Console.WriteLine("reading site");
        //load the return text to the htmlbuffer, if buffer is full wait for space
        await htmlbuffer.Writer.WriteAsync(await html.Content.ReadAsStringAsync());
        Console.WriteLine("reading site complete");
    }
}
 async Task Process()
{
    //while the buffer hasn't closed, wait for new data to be available
    while (await htmlbuffer.Reader.WaitToReadAsync())
    {
        //read html from buffer send to doSomething then read next element
        var html = await htmlbuffer.Reader.ReadAsync();
        await doSomethingWithHTML(html);
    }
}

async Task doSomethingWithHTML(string html)
{
    await Task.Delay(2);
    Console.WriteLine("done something");
}

//start 4 feeders threads
var feeders = new[]
{
    Feed(),
    Feed(),
    Feed(),
    Feed(),
};
//start 2 worker threads
var workers = new[]
{
    Process(),
    Process(),
};
//start of feeding in sites
foreach (var item in sites)
{
    await sitebuffer.Writer.WriteAsync(item);
}
//mark that all sites have been fed into the systems
sitebuffer.Writer.Complete();

//wait for all feeders to finish
await Task.WhenAll(feeders);
//mark that no more sites will be read
htmlbuffer.Writer.Complete();
//wait for all workers to finish
await Task.WhenAll(workers);

Console.WriteLine("all tasks complete");
使用系统;
使用System.Threading.Channel;
使用System.Net.Http;
使用System.Collections.Generic;
使用System.Threading.Tasks;
使用System.Linq;
var sites=Enumerable.Range(0,5000)。选择(i=>@“http:\\www.example.com”);
//创建线程安全缓冲区大小没有限制
var sitebuffer=Channel.CreateUnbounded();
//创建限制为10个元素的线程安全缓冲区
var htmlbuffer=Channel.CreateBounded(10);
异步任务提要()
{
//当缓冲区尚未关闭时,等待新数据可用
while(等待sitebuffer.Reader.WaitToReadSync())
{
//从缓冲区读取下一个可用url
var uri=await sitebuffer.Reader.ReadAsync();
var http=new-HttpClient();
var html=await http.GetAsync(uri);
Console.WriteLine(“阅读网站”);
//如果缓冲区已满,请将返回文本加载到htmlbuffer,并等待空间
wait htmlbuffer.Writer.WriteAsync(wait html.Content.ReadAsStringAsync());
Console.WriteLine(“阅读站点完成”);
}
}
异步任务进程()
{
//当缓冲区尚未关闭时,等待新数据可用
while(等待htmlbuffer.Reader.WaitToReadAsync())
{
//从缓冲区读取html发送到doSomething,然后读取下一个元素
var html=await htmlbuffer.Reader.ReadAsync();
等待doSomethingWithHTML(html);
}
}
异步任务doSomethingWithHTML(字符串html)
{
等待任务。延迟(2);
Console.WriteLine(“做了某事”);
}
//启动4个进料器螺纹
无功馈线=新[]
{
Feed(),
Feed(),
Feed(),
Feed(),
};
//启动2个工作线程
var工人=新[]
{
进程(),
进程(),
};
//开始在现场喂食
foreach(站点中的var项目)
{
等待sitebuffer.Writer.WriteAsync(项);
}
//标记所有站点都已输入系统
sitebuffer.Writer.Complete();
//等待所有进料器完成
等待任务。WhenAll(喂食者);
//标记不再读取其他站点
htmlbuffer.Writer.Complete();
//等待所有工人完成
等待任务。何时(工人);
Console.WriteLine(“所有任务完成”);
注意
异步任务
等待
这是一个围绕线程的较新包装器