0

メッセージをデータベース (または他の場所) に記録する Windows サービス プロジェクトがあります。これらのメッセージの頻度は、1 秒あたり最大 10 になる可能性があります。メッセージの送信と処理によってサービスのメイン プロセスが遅延することはないため、すべてのメッセージを処理するために新しいスレッドを開始します。これは、メイン プロセスが 100 個のログ メッセージを送信する必要がある場合、各メッセージを処理する 100 個のスレッドが開始されることを意味します。スレッドが終了すると、スレッドがクリーンアップされるため、破棄する必要がないことを学びました。スレッド内のすべての使用済みオブジェクトを破棄する限り、すべてが正常に機能するはずです。

サービスは、サービスのシャットダウンにつながる例外に入る可能性があります。サービスがシャットダウンする前に、メッセージを記録していたすべてのスレッドを待機する必要があります。これを実現するために、スレッドが開始されるたびにスレッドをリストに追加します。wait-for-threads メソッドが呼び出されると、リスト内のすべてのスレッドがまだ生きているかどうかがチェックされ、生きている場合は join を使用して待機します。

コード:

スレッドの作成:

/// <summary>
    /// Creates a new thread and sends the message
    /// </summary>
    /// <param name="logMessage"></param>
    private static void ThreadSend(IMessage logMessage)
    {
        ParameterizedThreadStart threadStart = new ParameterizedThreadStart(MessageHandler.HandleMessage);
        Thread messageThread = new Thread(threadStart);
        messageThread.Name = "LogMessageThread";            
        messageThread.Start(logMessage);
        threads.Add(messageThread);
    }

スレッドが終了するのを待っています:

    /// <summary>
    /// Waits for threads that are still being processed
    /// </summary>
    public static void WaitForThreads()
    {
        int i = 0;
        foreach (Thread thread in threads)
        {
            i++;
            if (thread.IsAlive)
            {
                Debug.Print("waiting for {0} - {1} to end...", thread.Name, i);
                thread.Join();
            }
        }
    }

今、私の主な懸念は、このサービスが 1 か月間実行された場合でも、リストにすべてのスレッド (数百万) が残っていることです (ほとんどのスレッドが停止しています)。これはメモリを消費しますが、どれだけかわかりません。これは全体として私にとって良い習慣ではないようです。完成したスレッドをクリーンアップしたいのですが、その方法がわかりません。これには良い方法やベストプラクティスがありますか?

4

5 に答える 5

2

まず第一に、ログ メッセージごとに 1 つのスレッドを作成するのは得策ではありません。ThreadPool共通キュー (プロデューサー/コンシューマー) からのログ項目を処理する限られた数のワーカー スレッドを使用または作成します。

2 番目: もちろん、リストからスレッド参照も削除する必要があります。スレッド メソッドが終了すると、それ自体を削除するか、定期的に削除することもできます。たとえば、デッド スレッドのリストをチェックして削除するタイマーを 30 分ごとに実行します。

于 2013-09-23T08:24:55.233 に答える
2

死んでいるスレッドをリストから削除しますか?

/// <summary>
/// Waits for threads that are still being processed
/// </summary>
public static void WaitForThreads()
{
    List<Thread> toRemove = new List<int>();

    int i = 0;
    foreach (Thread thread in threads)
    {
        i++;
        if (thread.IsAlive)
        {
            Debug.Print("waiting for {0} - {1} to end...", thread.Name, i);
            thread.Join();
        }
        else
        {
            toRemove.Add(thread);
        }
    }
    threads.RemoveAll(x => toRemove.Contains(x));
}

タスクの並列処理を見てください

于 2013-09-23T08:16:03.783 に答える
1

これらのスレッドで行っているのがログ記録だけである場合は、ログ記録スレッドを 1 つと、メイン スレッドがメッセージを書き込む共有キューを用意する必要があります。その後、ログ スレッドはキューとログを読み取ることができます。これは、 BlockingCollectionを使用すると非常に簡単です。

サービスのメイン スレッドでキューを作成します。

BlockingCollection<IMessage> LogMessageQueue = new BlockingCollection<IMessage>();

サービスのメイン スレッドは、Loggerログ メッセージを処理するスレッドを開始するインスタンス (以下を参照) を作成します。メイン スレッドは、アイテムを に追加しますLogMessageQueue。ロガー スレッドはキューからそれらを読み取ります。メイン スレッドがシャットダウンする場合は、 を呼び出しますLogMessageQueue.CompleteAdding。ロガーはキューを空にして終了します。

メインスレッドは次のようになります。

// start the logger
Logger _loggingThread = new Logger(LogMessageQueue);

// to log a message:
LogMessageQueue.Add(logMessage);

// when the program needs to shut down:
LogMessageQueue.CompleteAdding();

そしてロガークラス:

class Logger
{
    BlockingCollection<IMessage> _queue;
    Thread _loggingThread;

    public Logger(BlockingCollection<IMessage> queue)
    {
        _queue = queue;
        _loggingThread = new Thread(LoggingThreadProc);
    }

    private void LoggingThreadProc(object state)
    {
        IMessage msg;
        while (_queue.TryTake(out msg, TimeSpan.Infinite))
        {
            // log the item
        }
    }
}

このようにして、追加のスレッドが1つだけになり、メッセージは送信された順序で処理されることが保証され(現在のアプローチには当てはまりません)、スレッドのシャットダウンなどを追跡することを心配する必要はありません.

アップデート

一部のログ メッセージの処理に時間がかかる場合 (たとえば、説明した電子メール)、それらを非同期で処理できます。例えば:

while (_queue.TryTake(out msg, TimeSpan.Infinite))
{
    if (msg.Type == Email)
    {
        // start asynchronous task to send email
    }
    else
    {
        // write to log file
    }
}

このようにして、多くの時間がかかる可能性のあるメッセージのみが非同期で実行されます。必要に応じて、電子メール メッセージ用にセカンダリ キューを配置することもできます。そうすれば、たくさんのメール スレッドに行き詰まることはありません。むしろ、それを 1 つまたは 2 つ、あるいはほんの一握りに制限します。

Logger必要に応じて複数のインスタンスを作成し、すべて同じメッセージ キューから読み取ることもできます。それぞれが別のログ ファイルに書き込んでいることを確認してください。キュー自体は複数のコンシューマーをサポートします。

于 2013-09-23T16:22:06.553 に答える
0

一般的に、問題を解決するためのアプローチはおそらくベストプラクティスではないと思います。つまり、何千ものスレッドを作成する代わりに、何千ものメッセージをデータベースに保存したいだけですよね? そして、これを非同期で行いたいようです。

しかし、メッセージごとにスレッドを作成することは本当に良い考えではなく、実際にはその問題を解決しません...

代わりに、メッセージ キューのようなものを実装しようとします。複数のキューを持つことができ、各キューには独自のスレッドがあります。メッセージが着信している場合は、それらをキューの 1 つに送信します (交互に)...

キューは、キューに入れられたメッセージをデータベースに格納しようとするまで、一定量のメッセージを待機するか、常に一定時間待機します (例: 1 秒。データベース内に 100 個のメッセージを格納するのにかかる時間によって異なります)。 . このようにして、実際には常に一定数のスレッドを使用する必要があり、パフォーマンスの問題は発生しません...

また、データベース接続などのオーバーヘッドで1つずつではなく、データをバッチ挿入することもできます...

当然のことながら、データベースが遅い場合、タスクはメッセージを保存でき、より多くのメッセージがキューに入れられます...しかし、それは現在のソリューションにも当てはまります。

于 2013-09-23T08:30:54.920 に答える
0

複数の回答とコメントが私の解決策につながったので、ここに完全なコードを投稿します。

スレッドプールを使用して、このページのスレッドとコードを管理し、関数を待機させました。

スレッドの作成:

private static void ThreadSend(IMessage logMessage)
        {    
            ThreadPool.QueueUserWorkItem(MessageHandler.HandleMessage, logMessage);
        }

スレッドが終了するのを待っています:

public static bool WaitForThreads(int maxWaitingTime)
        {
            int maxThreads = 0;
            int placeHolder = 0;
            int availableThreads = 0;

            while (maxWaitingTime > 0)
            {
                System.Threading.ThreadPool.GetMaxThreads(out maxThreads, out placeHolder);
                System.Threading.ThreadPool.GetAvailableThreads(out availableThreads, out placeHolder);

                //Stop if all threads are available
                if (availableThreads == maxThreads)
                {
                    return true;
                }

                System.Threading.Thread.Sleep(TimeSpan.FromMilliseconds(1000));
                --maxWaitingTime;            
            }

            return false;
        }

必要に応じて、これらのメソッドの外にこれを追加して、プール内のスレッドの量を制限できます。

System.Threading.ThreadPool.SetMaxThreads(MaxWorkerThreads, MaxCompletionPortThreads);
于 2013-09-25T13:08:43.773 に答える