[英]Thread-safe buffer for .NET
(注意:雖然我想了解.Net 4.0的未來想法,但我只限於此項目的.Net 3.5。)
我有一個線程,它從外部設備異步讀取數據(在代碼示例中通過創造性的strSomeData :-)模擬並將其存儲在StringBuilder'緩沖區'中(strBuilderBuffer :-)
在'主要代碼'中,我想在這個'緩沖區'''嘮叨'。 但是,從“操作”的角度來看,我不確定如何以線程安全的方式執行此操作。 我理解從“數據”的角度看它是安全的,因為根據msdn,“這個( StringBuilder
)類型的任何公共靜態成員都是線程安全的。任何實例成員都不能保證是線程安全的。” 但是,我的下面的代碼說明從“運營”角度來看它可能不是線程安全的。
關鍵是我擔心兩行代碼:
string strCurrentBuffer = ThreadWorker_TestThreadSafety_v1a.strBuilderBuffer.ToString();
// Thread 'randomly' slept due to 'inconvenient' comp resource scheduling...
ThreadWorker_TestThreadSafety_v1a.strBuilderBuffer.Length = 0;
如果計算機操作系統在緩沖區的“讀取”和緩沖區的“清除”之間休眠,我可能會丟失數據(這很糟糕:-(
有沒有辦法保證'atomocy?' 這兩行並迫使計算機不打斷它們?
關於弗拉德下面關於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();
}
}
}
在此處下載3.5版本的Reactive Extensions backport。 還有一個NuGet包。 下載后,只需在項目中引用System.Threading.dll即可。
現在,您可以在.NET 3.5中使用.NET 4.0中的所有新並發集合標准。 最適合您情況的是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>
類與BlockingCollection
基本相同。 它將在.NET 4.5中提供。
使用StringBuffer
不是線程安全的,但您可以切換到ConcurrentQueue<char>
。
如果您需要其他數據結構,.NET 4中還有其他線程安全的集合,請參閱http://msdn.microsoft.com/en-us/library/dd997305.aspx 。
編輯:在.NET 3.5中,同步原語較少。 您可以通過在Queue<char>
周圍添加一個鎖來創建一個簡單的解決方案,盡管它的效率低於.NET 4的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...
編輯:
您無法保證操作系統不會掛起持有鎖的工作線程。 但是,只要一個線程正在處理它,鎖就會保證其他線程無法干涉並更改緩沖區。
這就是為什么你拿鎖的時間應該盡可能短:
如果你從緩沖區中讀取大量內容,這可能會有所幫助:
http://msdn.microsoft.com/en-us/library/system.threading.readerwriterlock.aspx
可以有多個讀者,但只有一個作者。
它在.NET 1.X及以上版本中可用...
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.