0

独自のロギングフレームワークを実装しています。以下は、ログエントリを受け取り、メソッドBaseLoggerを実装する実際のロガーにプッシュするmyです。abstract Log

非同期方式でのロギングにはC#TPLを使用します。TPLの代わりにスレッドを使用しています。(TPLタスクは実際のスレッドを保持しません。したがって、アプリケーションのすべてのスレッドが終了すると、タスクも停止し、すべての「待機中」のログエントリが失われます。)

public abstract class BaseLogger
{
    // ... Omitted properties constructor .etc. ... //

public virtual void AddLogEntry(LogEntry entry)
{
    if (!AsyncSupported)
    {
        // the underlying logger doesn't support Async.
        // Simply call the log method and return.
        Log(entry);
        return;
    }
    // Logger supports Async.
    LogAsync(entry);
}

private void LogAsync(LogEntry entry)
{
    lock (LogQueueSyncRoot) // Make sure we ave a lock before accessing the queue.
    {
        LogQueue.Enqueue(entry);
    }

    if (LogThread == null || LogThread.ThreadState == ThreadState.Stopped)
    { // either the thread is completed, or this is the first time we're logging to this logger.
        LogTask = new  new Thread(new ThreadStart(() =>
        {
            while (true)
            {
                LogEntry logEntry;
                lock (LogQueueSyncRoot)
                {
                    if (LogQueue.Count > 0)
                    {
                        logEntry = LogQueue.Dequeue();
                    }
                    else
                    {
                        break; 
                        // is it possible for a message to be added,
                        // right after the break and I leanve the lock {} but 
                        // before I exit the loop and task gets 'completed' ??
                    }
                }
                Log(logEntry);
             }
        }));
        LogThread.Start();
    }
}

// Actual logger implimentations will impliment this method.
protected abstract void Log(LogEntry entry);
}

AddLogEntry複数のスレッドから同時に呼び出すことができることに注意してください。

私の質問は、この実装がログエントリを失う可能性はありますか?私のスレッドがbreakステートメントを含むループを存在し、ロックブロックを終了した直後に、キューにログエントリを追加することは可能ですか?これはelse句にあり、スレッドはまだ「実行中」の状態。

キューを使用しているため、エントリを見逃した場合でも、次にログに記録するリクエストによって、失われたエントリもプッシュされることを認識しています。ただし、これは、特にアプリケーションの最後のログエントリで発生する場合は、受け入れられません。

また、同じものを実装できるかどうか、どのように実装できるかを教えてください。ただし、新しいC#5.0asyncawaitキーワードをよりクリーンなコードで使用します。.NET4.5が必要でもかまいません。

前もって感謝します。

4

3 に答える 3

4

これを機能させることはできますが、私の経験では、可能であれば、既存のロギングフレームワークを使用することをお勧めします:)たとえば、log4netを使用した非同期ロギング/アペンダーには、この非同期アペンダーラッパーなどのさまざまなオプションがあります。 。

そうでなければ、とにかくロギング操作中にスレッドプールスレッドをブロックすることになるので、私は代わりにロギング専用のスレッドを開始します。何もログに記録されていないときにスレッドプールスレッドを保持しないように、タスクを介して、すでにそのアプローチを採用しているようです。ただし、実装を簡素化することで、専用のスレッドを使用するだけでメリットが得られると思います。

専用のロギングスレッドができたら、中間のConcurrentQueueだけが必要です。その時点で、logメソッドはキューに追加するだけで、専用のロギングスレッドはすでに持っているwhileループを実行します。ブロッキング/制限された動作が必要な場合は、 BlockingCollectionでラップできます。

専用スレッドを唯一の書き込みとして持つことにより、複数のスレッド/タスクがキューエントリをプルオフし、同時にログエントリを書き込もうとする可能性を排除します(競合状態の悪化)。logメソッドはコレクションに追加されているだけなので、非同期である必要はなく、TPLを処理する必要もまったくありません。これにより、推論がより簡単になります(できれば、「明らかに正しい」またはその周辺:)

この「専用のロギングスレッド」アプローチは、例として役立つ場合に備えて、リンクしたlog4netアペンダーであるFWIWも同様に機能すると信じています。

于 2012-08-25T05:14:20.323 に答える
3

頭のてっぺんから2つの競合状態が見られます。

  1. Thread複数のスレッドがを呼び出す場合は、複数のスレッドを起動できますAddLogEntry。これによりイベントが失われることはありませんが、非効率的です。
  2. はい、イベントのThread終了中にイベントをキューに入れることができます。その場合、イベントは「失われます」。

また、ここには重大なパフォーマンスの問題があります。常に(1秒間に数千回)ログを記録しない限り、Threadログエントリごとに新しいログが表示されます。それはすぐに高価になります。

Jamesのように、確立されたロギングライブラリを使用する必要があることに同意します。ロギングは見た目ほど簡単ではなく、すでに多くの解決策があります。

とはいえ、優れた.NET 4.5ベースのアプローチが必要な場合は、非常に簡単です。

public abstract class BaseLogger
{
  private readonly ActionBlock<LogEntry> block;

  protected BaseLogger(int maxDegreeOfParallelism = 1)
  {
    block = new ActionBlock<LogEntry>(
        entry =>
        {
          Log(entry);
        },
        new ExecutionDataflowBlockOptions
        {
          MaxDegreeOfParallelism = maxDegreeOfParallelism,
        });
  }

  public virtual void AddLogEntry(LogEntry entry)
  {
    block.Post(entry);
  }

  protected abstract void Log(LogEntry entry);
}
于 2012-08-25T10:43:17.963 に答える
0

未処理の例外が原因でアプリクラッシュの待機メッセージが失われることに関して、ハンドラーをイベントにバインドしましたAppDomain.CurrentDomain.DomainUnload。このようになります:

protected ManualResetEvent flushing = new ManualResetEvent(true);
protected AsyncLogger()  // ctor of logger
{
    AppDomain.CurrentDomain.DomainUnload += CurrentDomain_DomainUnload;
}

protected void CurrentDomain_DomainUnload(object sender, EventArgs e)
{
    if (!IsEmpty)
    {
        flushing.WaitOne();
    }
}

多分あまりきれいではありませんが、動作します。

于 2013-06-18T05:46:53.287 に答える