3

(注: .Net 4.0 の将来のアイデアが欲しいのですが、このプロジェクトでは .Net 3.5 に限定されています。)

外部デバイスからデータを非同期的に読み取り (これまでになく創造的な strSomeData によるコード例でシミュレートされています:-)、それを StringBuilder の「バッファ」(strBuilderBuffer :-) に格納するスレッドがあります。

「メイン コード」では、この「バッファ」を「ニブル」したいと考えています。ただし、「操作」の観点から、スレッドセーフな方法でこれを行う方法についてはわかりません。msdn によると、「この ( StringBuilder) 型の public static メンバーはすべてスレッド セーフです。インスタンス メンバーは、スレッド セーフであることが保証されていません。」ただし、以下のコードは、「運用」の観点から、スレッドセーフではない可能性があることを示しています。

重要なのは、次の 2 行のコードが気になるということです。

string strCurrentBuffer = ThreadWorker_TestThreadSafety_v1a.strBuilderBuffer.ToString();
// Thread 'randomly' slept due to 'inconvenient' comp resource scheduling...
ThreadWorker_TestThreadSafety_v1a.strBuilderBuffer.Length = 0;

コンピューターの OS がバッファーの「読み取り」とバッファーの「クリア」の間にスレッドをスリープ状態にすると、データが失われる可能性があります (これは悪いことです :-(

「原子性」を保証する方法はありますか? これらの2行のうち、コンピューターがそれらを中断しないように強制しますか?

の使用に関する以下のVladの提案に関してlock、私はそれを試しましたが、うまくいきませんでした(実際にはまったく):

    public void BufferAnalyze()
    {
        String strCurrentBuffer;
        lock (ThreadWorker_TestThreadSafety_v1a.strBuilderBuffer)
        {
            strCurrentBuffer = ThreadWorker_TestThreadSafety_v1a.strBuilderBuffer.ToString();
            Console.WriteLine("[BufferAnalyze()]  ||<<  Thread 'Randomly' Slept due to comp resource scheduling");
            Thread.Sleep(1000);  //  Simulate poor timing of thread resourcing...
            ThreadWorker_TestThreadSafety_v1a.strBuilderBuffer.Length = 0;
        }
        Console.WriteLine("[BufferAnalyze()]\r\nstrCurrentBuffer[{0}] == {1}", strCurrentBuffer.Length.ToString(), strCurrentBuffer);
    }

スレッドセーフバッファを実装するより良い方法はありますか?

完全なコードは次のとおりです。

namespace ExploringThreads
{
    /// <summary>
    /// Description of BasicThreads_TestThreadSafety_v1a
    /// </summary>
    class ThreadWorker_TestThreadSafety_v1a
    {
        private Thread thread;
        public static StringBuilder strBuilderBuffer = new StringBuilder("", 7500);
        public static StringBuilder strBuilderLog = new StringBuilder("", 7500);

        public bool IsAlive
        {
            get { return thread.IsAlive; }
        }

        public ThreadWorker_TestThreadSafety_v1a(string strThreadName)
        {
            // It is possible to have a thread begin execution as soon as it is created.
            // In the case of MyThread this is done by instantiating a Thread object inside MyThread's constructor.
            thread = new Thread(new ThreadStart(this.threadRunMethod));
            thread.Name = strThreadName;
            thread.Start();
        }

        public ThreadWorker_TestThreadSafety_v1a() : this("")
        {
            //   NOTE:  constructor overloading ^|^
        }

        //Entry point of thread.
        public void threadRunMethod()
        {
            Console.WriteLine("[ThreadWorker_TestThreadSafety_v1a threadRunMethod()]");
            Console.WriteLine(thread.Name + " starting.");
            int intSomeCounter = 0;
            string strSomeData = "";
            do
            {
                Console.WriteLine("[ThreadWorker_TestThreadSafety_v1a threadRunMethod()] running.");
                intSomeCounter++;
                strSomeData = "abcdef" + intSomeCounter.ToString() + "|||";
                strBuilderBuffer.Append(strSomeData);
                strBuilderLog.Append(strSomeData);
                Thread.Sleep(200);
            } while(intSomeCounter < 15);

            Console.WriteLine(thread.Name + " terminating.");
        }
    }
    /// <summary>
    /// Description of BasicThreads_TestThreadSafety_v1a.
    /// </summary>
    public class BasicThreads_TestThreadSafety_v1a
    {
        public BasicThreads_TestThreadSafety_v1a()
        {
        }

        public void BufferAnalyze()
        {
            string strCurrentBuffer = ThreadWorker_TestThreadSafety_v1a.strBuilderBuffer.ToString();
            Console.WriteLine("[BufferAnalyze()]  ||<<  Thread 'Randomly' Slept due to comp resource scheduling");
            Thread.Sleep(1000);  //  Simulate poor timing of thread resourcing...
            ThreadWorker_TestThreadSafety_v1a.strBuilderBuffer.Length = 0;
            Console.WriteLine("[BufferAnalyze()]\r\nstrCurrentBuffer[{0}] == {1}", strCurrentBuffer.Length.ToString(), strCurrentBuffer);
        }

        public void TestBasicThreads_TestThreadSafety_v1a()
        {
            Console.Write("Starting TestBasicThreads_TestThreadSafety_v1a  >>>  Press any key to continue . . . ");
            Console.Read();

            // First, construct a MyThread object.
            ThreadWorker_TestThreadSafety_v1a threadWorker_TestThreadSafety_v1a = new ThreadWorker_TestThreadSafety_v1a("threadWorker_TestThreadSafety_v1a Child");

            do
            {
                Console.WriteLine("[TestBasicThreads_TestThreadSafety_v1a()]");
                Thread.Sleep(750);
                BufferAnalyze();
                //} while (ThreadWorker_TestThreadSafety_v1a.thread.IsAlive);
            } while (threadWorker_TestThreadSafety_v1a.IsAlive);
            BufferAnalyze();
            Thread.Sleep(1250);
            Console.WriteLine("[TestBasicThreads_TestThreadSafety_v1a()]");
            Console.WriteLine("ThreadWorker_TestThreadSafety_v1a.strBuilderLog[{0}] == {1}", ThreadWorker_TestThreadSafety_v1a.strBuilderLog.Length.ToString(), ThreadWorker_TestThreadSafety_v1a.strBuilderLog);

            Console.Write("Completed TestBasicThreads_TestThreadSafety_v1a  >>>  Press any key to continue . . . ");
            Console.Read();
        }
    }
}
4

3 に答える 3

4

3.5 用のReactive Extensionsバックポートをここからダウンロードしてください。そのためのNuGetパッケージもあります。ダウンロードしたら、プロジェクトで System.Threading.dll を参照するだけです。

.NET 4.0 の新しい並行コレクション標準をすべて .NET 3.5 でも使用できるようになりました。あなたの状況に最適なのはBlockingCollectionです。これは基本的に、スレッドがアイテムをキューに入れたり、通常のキューのようにデキューできるようにするバッファです。アイテムが利用可能になるまでデキュー操作がブロックされることを除いて。

StringBuilder現在、クラスを使用する必要はまったくありません。コードをリファクタリングする方法は次のとおりです。理解しやすいように、例を短くしてみました。

public class Example
{
  private BlockingCollection<string> buffer = new BlockingCollection<string>();

  public Example()
  {
    new Thread(ReadFromExternalDevice).Start();
    new Thread(BufferAnalyze).Start();
  }

  private void ReadFromExteneralDevice()
  {
    while (true)
    {
      string data = GetFromExternalDevice();
      buffer.Add(data);
      Thread.Sleep(200);
    }
  }

  private void BufferAnalyze()
  {
    while (true)
    {
      string data = buffer.Take(); // This blocks if nothing is in the queue.
      Console.WriteLine(data);
    }
  } 
}

今後の参考のために、 TPL データ フローBufferBlock<T>ライブラリのクラスは基本的に と同じことを行います。.NET 4.5 で利用できるようになります。BlockingCollection

于 2012-05-08T16:11:52.713 に答える
3

UsingStringBufferはスレッドセーフではありませんが、 に切り替えることができますConcurrentQueue<char>

他のデータ構造が必要な場合は、.NET 4 に他のスレッド セーフ コレクションがあります。 http://msdn.microsoft.com/en-us/library/dd997305.aspxを参照してください。


編集: .NET 3.5 では、同期プリミティブが少なくなります。周りにロックを追加することで簡単な解決策を作ることができますがQueue<char>、.NET 4 の .NET よりも効率的ではありませんConcurrentQueue。または、読み取り/書き込み操作StrignBufferで同じを使用します。lock

public static StringBuilder strBuilderBuffer = new StringBuilder("", 7500);
private object BufferLock = new object();

...

lock (BufferLock)
    strBuilderBuffer.Append(strSomeData);

...

string strCurrentBuffer;
lock (BufferLock)
{
    strCurrentBuffer = ThreadWorker_TestThreadSafety_v1a.strBuilderBuffer.ToString();
    ThreadWorker_TestThreadSafety_v1a.strBuilderBuffer.Clear();
}
Console.WriteLine("[BufferAnalyze()]  ||<<  Thread 'Randomly' Slept ...");
Thread.Sleep(1000);  //  Simulate poor timing of thread resourcing...

編集:

OS がロックを保持している作業スレッドを中断しないことを保証することはできません。ただし、ロックは、1 つのスレッドがバッファを処理している限り、他のスレッドが干渉してバッファを変更できないことを保証します。

そのため、ロックを保持する時間をできるだけ短くする必要があります。

  • ロックの取得、データの追加、ロックの解放、または
  • ロックを取得し、データをコピーし、バッファを空にし、ロックを解放し、コピーされたデータの処理を開始しました。
于 2012-05-08T15:14:34.580 に答える
1

バッファから多くの読み取りを行っている場合は、おそらくこれが役立ちます。

http://msdn.microsoft.com/en-us/library/system.threading.readerwriterlock.aspx

複数のリーダーが可能ですが、ライターは 1 つだけです。

.NET 1.X 以降で利用できます...

于 2012-05-08T15:38:38.537 に答える