C# 停止线程,直到有足够的内存可用

C# 停止线程,直到有足够的内存可用,c#,xml,out-of-memory,C#,Xml,Out Of Memory,环境:.net 4.0 我有一个任务,用XSLT样式表转换XML文件,下面是我的代码 public string TransformFileIntoTempFile(string xsltPath, string xmlPath) { var transform = new MvpXslTransform(); transform.Load(xsltPath, new XsltSettings(true, false), new XmlUrlResol

环境:.net 4.0

我有一个任务,用XSLT样式表转换XML文件,下面是我的代码

public string TransformFileIntoTempFile(string xsltPath, 
    string xmlPath)
{
    var transform = new MvpXslTransform();
    transform.Load(xsltPath, new XsltSettings(true, false), 
        new XmlUrlResolver());

    string tempPath = Path.GetTempFileName();

    using (var writer = new StreamWriter(tempPath))
    {
        using (XmlReader reader = XmlReader.Create(xmlPath))
        {
            transform.Transform(new XmlInput(reader), null, 
                new XmlOutput(writer));
        }       
    }

    return tempPath;
}
我有X个线程可以并行启动此任务。 有时我的输入文件大约是300 MB,有时只有几MB

我的问题是:当我的程序试图同时转换一些大的XML文件时,我会遇到OutOfMemoryException

我怎样才能避免这些记忆之外的感受呢?我的想法是在执行任务之前停止线程,直到有足够的可用内存,但我不知道如何做到这一点。或者还有其他解决方案(比如将我的任务放在一个不同的应用程序中)


谢谢

自从.NET Framework 4以来,我们有了API来处理Win32API多年来提供的良好的旧功能,因此现在您可以从.NET托管代码中使用它

为了使任务更适合“持久内存映射文件”选项, :

持久化文件是与文件关联的内存映射文件 磁盘上的源文件。当最后一个进程完成使用时 该文件中,数据保存到磁盘上的源文件中。这些 内存映射文件适用于处理非常大的文件 源文件

在MethodDescription的页面上,您可以找到一个很好的示例,描述如何为这个非常大的文件创建内存映射视图

编辑:关于评论中大量注释的更新

刚找到一个方法,该方法创建一个从。 我相信您可以从这个流创建一个XmlReader实例,然后使用这个reader/流实例化XslTransform的自定义实现


EDIT2:remi bourgarel(OP)已经测试了这种方法,看起来这种特殊的XslTransform实现(我不知道是否有)不会以假定的方式与MM视图流一起工作

我不建议阻塞线程。在最坏的情况下,您最终将耗尽可能释放所需内存的任务,从而导致死锁或总体性能非常差

相反,我建议你保持一个有优先权的工作队列。通过线程池从队列中公平调度任务。确保没有线程阻塞等待操作,而是将任务重新发布到队列(优先级较低)

因此,您要做的(例如,在接收到OutOfMemory异常时)是将相同的作业/任务发布到队列中,并终止当前任务,释放线程以执行另一个任务


一种简单的方法是使用后进先出(LIFO),它可以确保发布到队列中的任务的优先级低于该队列中已有的任何其他作业。

主要问题是您正在加载整个Xml文件。如果只是在读取时进行转换,通常不会出现内存不足问题。 尽管如此,我还是发现了一篇MS支持文章,其中建议了如何做到这一点:


免责声明:我没有测试这个,所以如果你使用它,它工作,请让我们知道。

< P>你可以考虑使用队列来节制多少个并发转换是基于某种人工内存边界(例如文件大小)来完成的。可以使用以下类似的内容

这种节流策略可以与正在处理的最大并发文件数相结合,以确保您的磁盘不会受到太多的冲击

NB我没有在执行过程中加入必要的try\catch\finally,以确保将异常传播到调用线程,并始终释放Waithandles。我可以在这里进一步详述

public static class QueuedXmlTransform
{
    private const int MaxBatchSizeMB = 300;
    private const double MB = (1024 * 1024);
    private static readonly object SyncObj = new object();
    private static readonly TaskQueue Tasks = new TaskQueue();
    private static readonly Action Join = () => { };
    private static double _CurrentBatchSizeMb;

    public static string Transform(string xsltPath, string xmlPath)
    {
        string tempPath = Path.GetTempFileName();

        using (AutoResetEvent transformedEvent = new AutoResetEvent(false))
        {
            Action transformTask = () =>
            {
                MvpXslTransform transform = new MvpXslTransform();

                transform.Load(xsltPath, new XsltSettings(true, false),
                    new XmlUrlResolver());

                using (StreamWriter writer = new StreamWriter(tempPath))
                using (XmlReader reader = XmlReader.Create(xmlPath))
                {
                    transform.Transform(new XmlInput(reader), null,
                        new XmlOutput(writer));
                }

                transformedEvent.Set();
            };

            double fileSizeMb = new FileInfo(xmlPath).Length / MB;

            lock (SyncObj)
            {
                if ((_CurrentBatchSizeMb += fileSizeMb) > MaxBatchSizeMB)
                {
                    _CurrentBatchSizeMb = fileSizeMb;

                    Tasks.Queue(isParallel: false, task: Join);
                }

                Tasks.Queue(isParallel: true, task: transformTask);
            }

            transformedEvent.WaitOne();
        }

        return tempPath;
    }

    private class TaskQueue
    {
        private readonly object _syncObj = new object();
        private readonly Queue<QTask> _tasks = new Queue<QTask>();
        private int _runningTaskCount;

        public void Queue(bool isParallel, Action task)
        {
            lock (_syncObj)
            {
                _tasks.Enqueue(new QTask { IsParallel = isParallel, Task = task });
            }

            ProcessTaskQueue();
        }

        private void ProcessTaskQueue()
        {
            lock (_syncObj)
            {
                if (_runningTaskCount != 0) return;

                while (_tasks.Count > 0 && _tasks.Peek().IsParallel)
                {
                    QTask parallelTask = _tasks.Dequeue();

                    QueueUserWorkItem(parallelTask);
                }

                if (_tasks.Count > 0 && _runningTaskCount == 0)
                {
                    QTask serialTask = _tasks.Dequeue();

                    QueueUserWorkItem(serialTask);
                }
            }
        }

        private void QueueUserWorkItem(QTask qTask)
        {
            Action completionTask = () =>
            {
                qTask.Task();

                OnTaskCompleted();
            };

            _runningTaskCount++;

            ThreadPool.QueueUserWorkItem(_ => completionTask());
        }

        private void OnTaskCompleted()
        {
            lock (_syncObj)
            {
                if (--_runningTaskCount == 0)
                {
                    ProcessTaskQueue();
                }
            }
        }

        private class QTask
        {
            public Action Task { get; set; }
            public bool IsParallel { get; set; }
        }
    }
}

您使用的是哪个版本的.NET Framework?32位程序很少会缩小其VM大小或取消其地址空间的碎片。使用64位操作系统。@hans:是的,但正如你所知,从32位到64位是一种东西,我们实际上没有升级硬件/操作系统的计划。现在你只需要说服Xslt使用它。它与Xslt转换兼容吗。net在转换之前加载整个xml文件否?是否有证据表明xml类不会仅使用流将数据复制到内存中?@sll,我刚刚尝试了您的解决方案,Xslt转换仍然加载整个文档(我反编译了Xslt转换类,它使用XPathDocument加载整个xml)@sll,对于所有针对.net的xslt实现(无论是否为mvp),xslt都需要加载完整的XML文档。不,我不能重写Xslt的一个实现,对我来说似乎不是几小时的工作。你有这种队列的例子吗?我建议坚持使用TPL,例如,你链接中的代码只是建议使用XPathDocument,我不认为它真的是流式的。“阅读时转换”我认为可能不适用于XSLT转换,因为XPath查询可以跨越整个xml树。
_CurrentBatchSizeMb = fileSizeMb;