1

私のコンソールアプリはテキストファイルから大量のデータを読み込んでおり、それらはDBに保存されます。この目的のために、データを DataTable に保存しており、この DataTable を 5 分ごとに DB にダンプしたいと考えています (データ全体を一度にダンプしたい場合は、DataTable にデータ セット全体を入力する必要があります。その場合、私は OutOfMemoryException を取得しています)。

public void ProcessData()
{
    string[] files=File.ReadAllLines(path)
    foreach(var item in files)
    {
        DataRow dtRow= dataTable.NewRow();
        dtRow["ID"]= .... //some code here;
        dtRow["Name"]= .... //some code here;
        dtRow["Age"]= .... //some code here;

        var timer = new Timer(v => SaveData(), null, 0, 5*60*1000);
    }
}

public void SaveData(string tableName, DataTable dataTable )
{
    //Some code Here
    //After dumping data to DB, clear DataTable
    dataTable.Rows.Clear();
}

ここで私が望んでいたのは、コードが引き続き DataTable を埋め、5 分ごとに SaveData() メソッドを呼び出すことです。これは、すべてのファイルが処理されるまで実行され続けます。

ただし、SaveData() メソッドが呼び出されると、4 ~ 5 回実行されることがわかりました。5分ごとにボットが呼び出されることもあります。

ここでの進め方がわかりません。これを修正するには?ここで他のアプローチを使用できますか? どんな助けでも大歓迎です。

4

3 に答える 3

4

ReadAllLines を使用して各テキスト ファイルを完全に読み取ることが不可欠ですか。これは大量のメモリを消費します。ファイルから x 行を読み取り、データベースに保存し、ファイルの最後に到達するまで続行しないのはなぜですか?

于 2013-03-17T14:20:18.230 に答える
3

最大の問題はTimer、foreach で新しいインスタンスをインスタンス化することです。Timerすべての foreach 呼び出しの新しいオブジェクトは、複数のスレッドがSaveData同時に呼び出されることを意味dataTableします。つまり、行がクリアされる前に、おそらく (そしておそらく) 複数回同時に処理されてデータベースに保存されることを意味し、ファイルの大部分がデータベースに複製されます。

尋ねられた質問に対する解決策を提供する前に、5 分間隔でデータを保存すると、明確なコードの匂いがすることを指摘したいと思います。指摘されているように、任意の時間間隔ではなく、データ サイズに基づいてデータをロードおよび保存するアプローチをお勧めします。とはいえ、5分間のインターバルセーブをしなければならない理由があるという前提で、あなたの質問に答えます。

まず、Timer正しくセットアップする必要があります。これは、foreach ループの外側で作成していることに気付くでしょう。 Timer一度待機して実行するだけでなく、間隔を置いて実行を続けます。

第 2 に、中間データ ストアでスレッド セーフなデータ整合性を確保するための手順を実行する必要があります (あなたの場合は を使用しましたが、カスタム クラスの を使用しています。これは、やりたいことに対してコストがかかりすぎるためDataTableです)。を更新する前にロックすることでこれを達成していることに気付くでしょう。ListDataTableList

データ処理クラスの更新:

private bool isComplete = false;
private object DataStoreLock = new object();
private List<MyCustomClass> myDataStore;
private Timer myTimer;

public void ProcessData()
{
    myTimer = new Timer(SaveData, null, TimeSpan.Zero, TimeSpan.FromMinutes(5.0));
    foreach (var item in File.ReadLines(path))
    {
        var myData = new MyCustomClass()
            {
                ID = 0, // Some code here
                Name = "Some code here",
                Age = 0 // Some code here
            };
        lock (DataStoreLock)
        {
            myDataStore.Add(myData);
        }
    }
    isComplete = true;
}

public void SaveData(object arg)
{
    // Our first step is to check if timed work is done.
    if (isComplete)
    {
        myTimer.Dispose();
        myTimer = null;
    }
    // Our next step is to create a local instance of the data store to work on, which
    // allows ProcessData to continue populating while our DB actions are being performed.
    List<MyCustomClass> lDataStore;
    lock (DataStoreLock)
    {
        lDataStore = myDataStore;
        myDataStore = new List<MyCustomClass>();
    }
    //Some code DB code here.
}

編集:ReadLinesではなく通過するように列挙を変更しましたReadAllLinesReadLinesMSDNのメソッドの下にある備考をお読みください。ReadAllLinesブロッキング呼び出しにReadLinesなりますが、ファイルの読み取り中に列挙を処理できます。foreachファイルがすべて既にメモリに読み込まれている場合、5 分以上実行されるシナリオは想像できません。

于 2013-03-17T15:23:43.320 に答える
2

コードを実装する方法と他の回答からの提案についての提案は次のとおりです。

    public void ProcessData()
    {
        int i = 1;
        foreach(var item in File.ReadLines(path)) //This line has been edited
        {
            DataRow dtRow= dataTable.NewRow();
            dtRow["ID"]= .... //some code here;
            dtRow["Name"]= .... //some code here;
            dtRow["Age"]= .... //some code here;
            if (i%25 == 0) //you can change the 25 here to something else
            {
                SaveData(/* table name */, /* dataTable */);
            }
            i++;
        }
        SaveData(/* table name */, /* dataTable */);
    }

    public void SaveData(string tableName, DataTable dataTable )
    {
        //Some code Here
        //After dumping data to DB, clear DataTable
        dataTable.Rows.Clear();
    }
于 2013-03-17T15:05:14.183 に答える