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

时间:2011-10-31 11:13:59

标签: 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 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。

如何避免这些OutOfMemoryEception?我的想法是在执行任务之前停止一个线程,直到有足够的可用内存,但我不知道该怎么做。或者还有一些其他解决方案(比如将我的任务放在一个独特的应用程序中)。

由于

4 个答案:

答案 0 :(得分:2)

我不建议阻止线程。在最坏的情况下,你最终会饿死可能释放所需内存的任务,导致一般的死锁或非常糟糕的性能。

相反,我建议您保留优先级的工作队列。从线程池中公平调度的队列中获取任务。 确保没有线程在等待操作上阻塞,而是将任务重新发送到队列(优先级较低)

所以你要做的事情(例如,在收到OutOfMemory异常时),将相同的作业/任务发布到队列中并终止当前任务,释放线程以进行另一项任务。

一种简单的方法是使用LIFO,确保发布到队列的任务具有比该队列上已有的任何其他作业“更低的优先级”。

答案 1 :(得分:1)

从.NET Framework 4开始,我们有API可以使用Win32API中可用多年的良好旧Memory-Mapped Files功能,所以现在您可以从.NET托管代码中使用它。

为您的任务更好地适应“持久内存映射文件”选项, MSDN

  

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

MemoryMappedFile.CreateFromFile()方法描述的页面上,您可以找到一个很好的示例,描述如何为极大文件创建内存映射视图。

编辑:有关评论中可观笔记的更新

刚刚找到方法MemoryMappedFile.CreateViewStream(),它创建了一个继承自MemoryMappedViewStreamSystem.IO.Stream类型的流。 我相信你可以从这个流创建一个XmlReader实例,然后使用这个reader / stream实例化你的XslTransform的自定义实现。

EDIT2: remi bourgarel(OP)已经测试了这种方法,看起来像这个特定的XslTransform实现(我想知道是否会)将不会使用MM-View流的方式,这应该是

答案 2 :(得分:0)

主要问题是您正在加载整个Xml文件。如果您只是按原样进行转换,则通常不应出现内存不足问题。 话虽如此,我发现了一篇MS支持文章,建议如何做到: http://support.microsoft.com/kb/300934

免责声明:我没有对此进行测试,所以如果你使用它并且它有效,请告诉我们。

答案 3 :(得分:0)

您可以考虑使用队列来根据某种人工内存边界来控制正在进行的并发转换的数量,例如:文件大小。可以使用以下内容。

这种限制策略可以与正在处理的最大并发文件数量相结合,以确保您的磁盘不会被打得过多。

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; }
        }
    }
}

<强>更新

修正了滚动到下一个批处理窗口时维护批量大小的错误:

_CurrentBatchSizeMb = fileSizeMb;