0

ユーザーが行ごとまたはバッチでログを記録できる単純なログフレームワークを作成しています。コード内のユーザー Write(message) と bool flushEveryLine (各行で true、バッチ書き込みで false) に基づいて、ログは flush() メソッドを呼び出すことによって書き込まれます。メッセージはメイン スレッドで作成され、ファイルに書き込まれると、outputConsole または DebugConsole は Task を使用して別のスレッドになります。リストは、TPL を使用してメイン スレッドとワーカー スレッド間で同期したいものです。そのため、TimerEvent が発生すると、失われたメッセージが出力に書き込まれます。問題はメッセージの変更です。以下のメッセージを参照してください。

    protected virtual void Write(LogMessage message)
    {
        if (!this.IsEnabled) // log enabled or disabled.
            return;
        List<Task> list = new List<Task>();
        foreach (Task task in _taskList)
        {
            if (task.Status != TaskStatus.RanToCompletion)
                list.Add(task);
        }
        _taskList = list;
        Task.WaitAll(_taskList.ToArray());
       lock(storage)
        {
            //Add message to messages
           **this.messages.Add(message);**

            if (FlushEveryLine)
                **Flush();**
        } 

    public void Flush()
    {

        if (messages.Count == 0)
            return;
        _taskToMessagesMap.Add(counter, this.messages);
       **Task.Factory.StartNew(() => FlushData(counter));**
        ++counter;
        if (counter > 30000) // reset counter after large increment.
        {
            counter = 0;
        }
        this.messages.Clear();// clear the messages to record new set of messages before writing.
    }

    protected virtual void FlushData(int index)
    {
        **List<LogMessage> messages = _taskToMessagesMap[index];**
        if (!IsEnabled) // logs enabled or not.
        {
            return;
        }
        lock (messages)
        {
            if (messages.Count == 0)
            {
                return;
            }
            IEnumerable<LogMessage> temp = messages.OrderBy(msg => msg.TimeStamp).ToArray();
             this.flush(temp);

        }
        return;

    }

コードの説明:: _taskToMessagesMap = new Dictionary (); // カウンタとメッセージのリストを格納する辞書。リストの動作をテストしたいので、これはハックです。

Task.Factory.StartNew(() => FlushData(カウンター)); //実際には、FlushData() が呼び出されたときに渡されたメッセージを使用する必要があるように、各スレッドに Flush(messages) を使用したいと考えています。しかし、ワーカー スレッドが実行されると、現在のメッセージ リストが使用されます。リストが参照渡しであることはわかっているため、この動作が見られますが、上記の例では整数でもこの動作が見られます。例: 私の辞書には 1 つのキー {0} とすべてのテスト メッセージしかありませんが、ワーカー スレッドに入るとキー 1 を探します。なぜですか? 整数パラメータは値渡しである必要があります。ワーカー スレッドを作成するときに、パラメーターとして 0 を渡しています。どうすればこれを修正できますか。

困っているので助けてください。

4

1 に答える 1

2

クロージャーは、値ではなく変数を閉じます。ラムダが渡される前に実行中FlushDataの変数をインクリメントする命令が実行されるため、新しい値が取得されます。counterStartNew

これを回避する簡単な方法は、ローカル変数の現在の値のスナップショットを取得することです。

int currentCounter = counter;
Task.Factory.StartNew(() => FlushData(currentCounter));
++counter;

アップデート

他のスレッドがメッセージの書き込みを妨害しないように、タスクが処理するデータのコピーを作成する必要があります。Flush と FlushData を次のように書き直します。

public void Flush()
{
    if (messages.Count == 0)
        return;

    // make a copy of the list.
    var currentMessages = messages.ToList();

    Task.Factory.StartNew(() => FlushData(currentMessages));
    messages.Clear();// clear the messages to record new set of messages before writing.
}

protected virtual void FlushData(IEnumerable<LogMessage> toBeFlushed)
{
    if (!IsEnabled) // logs enabled or not.
    {
        return;
    }

    IEnumerable<LogMessage> temp = toBeFlushed.OrderBy(msg => msg.TimeStamp).ToArray();
    this.flush(temp);
}

Flushstorageこれはどこからでも呼び出すことができるパブリック メソッドであるため、おそらくロックも取得する必要があります。現在、メッセージが失われる可能性があります。

于 2013-02-11T05:43:29.960 に答える