3

我有一个Stock类可以从一个文件(大约 100 MB)中加载大量股票数据历史记录。我有一个Pair类,它接受两个Stock对象并计算两者之间的一些统计关系,然后将结果写入文件。

在我的主要方法中,我有一个循环遍历一对股票(大约 500 个)的列表。它创建 2 个库存对象,然后从这两个对象中创建一个 pair 对象。此时,配对计算被写入文件,我完成了对象。我需要释放内存,以便继续下一个计算。

除了将 3 个对象设置为 null 之外,我还在循环末尾添加了以下两行:

GC.Collect(GC.MaxGeneration);
GC.WaitForPendingFinalizers();

跨过这两行似乎只是从每次循环迭代分配的 200-300 MB 中释放了 50 MB(从任务管理器中查看)。

该程序在给我一个系统内存不足异常之前做了大约八到十对。内存使用量稳步增加,直到它在大约 1.5 GB 时崩溃。(这是一台运行 Win7 Ultimate 的 8 GB 机器)

我对垃圾收集没有太多经验。难道我做错了什么?

这是我的代码,因为你问了:(注意:程序有两种模式,1> 添加模式,将新配对添加到系统中。2> 常规模式,根据filesystemwatcher事件实时更新配对文件。股票数据由外部应用程序更新称为 QCollector。)

这是MainForm在添加模式下运行的段:

foreach (string line in PairList)
{
    string[] tokens = line.Split(',');

    stockA = new Stock(QCollectorPath, tokens[0].ToUpper()); 
    stockB = new Stock(QCollectorPath, tokens[1].ToUpper()); 

    double ratio = double.Parse(tokens[2]);
    Pair p = new Pair(QCollectorPath, stockA, stockB, ratio);

    // at this point the pair is written to file (constructor handles this)        

    // commenting out the following lines of code since they don't fix the problem
    // stockA = null;
    // stockB = null;
    // p = null;

    // refraining from forced collection since that's not the problem
    // GC.Collect(GC.MaxGeneration);
    // GC.WaitForPendingFinalizers();

    // so far this is the only way i can fix the problem by setting the pair classes
    // references to StockA and StockB to null
    p.Kill();
}

我正在根据请求添加更多代码:Stock并且Pair是 的子类TimeSeries,具有通用功能

public abstract class TimeSeries {
     protected List<string> data;

     // following create class must be implemented by subclasses (stock, pair, etc...)
     // as each class is created differently, although their data formatting is identical
     protected void List<string> Create();

     // . . . 

     public void LoadFromFile()
     {
          data = new List<string>();

          List<StreamReader> srs = GetAllFiles();

          foreach (StreamReader sr in srs)
          {
               List<string> temp = new List<string>();
               temp = TurnFileIntoListString(sr);
               data = new List<string>(temp.Concat(data));
               sr.Close()
          }
     }

     // uses directory naming scheme (according to data month/year) to find files of a symbol
     protected List<StreamReader> GetAllFiles()...

     public static List<string> TurnFileIntoListString(StreamReader sr)
     {
          List<string> list = new List<string>();
          string line;
          while ((line = sr.ReadLine()) != null)
               list.Add(line);
          return list;
     }

     // this is the only mean to access a TimeSeries object's data
     // this is to prevent deadlocks by time consuming methods such as pair's Create

     public string[] GetListCopy()
     {
          lock (data)
          {
               string[] listCopy = new string[data.count];
               data.CopyTo(listCopy);
               return listCopy();
          }
     }
}

public class Stock : TimeSeries
{
     public Stock(string dataFilePath, string symbol, FileSystemWatcher fsw = null)
     {
          DataFilePath = dataFilePath;
          Name = symbol.ToUpper();
          LoadFromFile();
          // to update stock data when external app updates the files
          if (fsw != null) fsw.Changed += FileSystemEventHandler(fsw_Changed);
     }

     protected void List<string> Create()
     {
          // stock files created by external application
     }


     // . . . 
}

public class Pair : TimeSeries {
     public Pair(string dataFilePath, Stock stockA, Stock stockB, double ratio)
     {
          // assign parameters to local members
          // ...         

          if (FileExists())
               LoadFromFile();
          else
             Create();
     }

     protected override List<string> Create()
     {
          // since stock can get updated by fileSystemWatcher's event handler
          // a copy is obtained from the stock object's data 
          string[] listA = StockA.GetListCopy();
          string[] listB = StockB.GetListCopy();
          List<string> listP = new List<string>();

          int i, j;
          i = GetFirstValidBar(listA);
          j = GetFirstValidBar(listB);
          DateTime dtA, dtB;

          dtA = GetDateTime(listA[i]);
          dtB = GetDateTime(listB[j]);

          // this hidden segment adjusts i and j until they are starting at same datetime
          // since stocks can have different amount of data

          while (i < listA.Count() && j < listB.Count)
          {
              double priceA = GetPrice(listA[i]);
              double priceB = GetPrice(listB[j]);
              double priceP = priceA * ratio - priceB;
              listP.Add(String.Format("{0},{1:0.00},{2:0.00},{3:0.00}"
                   , dtA
                   , priceP
                   , priceA
                   , priceB
              );
              if (i < j)
                   i++;
              else if (j < i)
                   j++;
              else
              {
                   i++; 
                   j++;
              }
          }
     }

     public void Kill()
     {
         data = null;
         stockA = null;
         stockB = null;
     }
 }
4

1 回答 1

3

您的内存泄漏在这里:

if (fsw != null) fsw.Changed += FileSystemEventHandler(fsw_Changed);

只要 FileSystemWatcher 处于活动状态,stock 对象的实例就会保存在内存中,因为它正在响应 FileSystemWatcher 的事件。

我认为您想在其他地方实现该事件,或者在代码中的其他位置添加:

if (fsw != null) fsw.Changed -= fsw_Changed;

考虑到代码的编写方式,在完成批量处理的情况下,可能会在没有 FileSystemWatcher 的情况下调用 stock 对象。

In the original code that you posted the constructors of the Stock classes were being called with a FileSystemWatcher. You have changed that now. I think you will find that now with a null FileSystemWatcher you can remove your kill and you will not have a leak since you are no longer listening to the fsw.Changed.

于 2012-07-25T01:02:53.713 回答