[英]Using a Concurrent Queue to pass objects between two components of my applciation
[英]Using a Queue to pass data between two BackgroundWorkers
我正在逐字節讀取和解碼二進制文件。 為此,我使用了兩個BackgroundWorker
:一個用於讀取文件,為文件的每個“行”生成一個大小可變的List<byte>
,另一個用於處理“行”。
由於我希望它們在parralel中運行,而且我不知道哪個會比另一個更快,因此我使用Queue
在兩個BackgroundWorker
之間傳遞數據。
事情是這樣: List<byte>
在任何時候都不應包含任何0
值。 在將它們添加到隊列之前,我會進行檢查。 盡管如此,在Queue
的另一端,某些列表仍包含0
值。 但是,我在每次調用Dequeue()
時都創建了一個新的List<byte>
,因為顯然,如果我不這樣做,則在處理完成之前會修改數據。
我嘗試手動創建一個新的List<byte>
對象, 然后將其分配給Dequeue()
結果,而沒有進行任何改進。 這是我第一次使用Queue
,並且由於我的代碼是多線程的,因此逐步調試幾乎是不可能的。
Queue<List<byte>> q = new Queue<List<byte>>(); // My FIFO queue
// Reading thread
private void BackgroudWorkerRead_DoWork(object sender, DoWorkEventArgs e)
{
// ... read the file
List<byte> line_list = new List<byte>();
// ... filling line_list with data
// in this part I check that no byte added to line_list has the value 0, or else I display an errror message and end the process
q.Enqueue(line_list);
if (!backgroundWorkerNewLine.IsBusy) backgroundWorkerNewLine.RunWorkerAsync(); // if the other BackgroundWorker isn't processing data, now it needs to since we just added some to the queue
}
// Processing thread
private void backgroundWorkerNewLine_DoWork(object sender, DoWorkEventArgs e)
{
while (q.Count > 0) // While there is data to process
{
string line_str = DecodeBytes(new List<byte>(q.Dequeue())); // Decoding
string[] elements = line_str.Split(separator, StringSplitOptions.None); // Separating values
Form1.ActiveForm.Invoke(new MethodInvoker(() => AddRow(elements))); // Add the line to a DataTable from the main thread
}
}
public string DecodeBytes(List<byte> line)
{
/// ... read each byte and return a string of the whole decoded line
}
public void AddRow(string[] el)
{
MyDataTable.Rows.Add(el);
}
似乎由q.Dequeue()返回的列表未返回與由q.Enqueue()添加的數據相同的數據
您應該使用Microsoft的Reactive Framework(aka Rx)-NuGet System.Reactive.Windows.Forms
(假設您正在編寫WinForms應用程序),並using System.Reactive.Linq;
添加using System.Reactive.Linq;
。
Rx讓您使用熟悉的LINQ語法進行並行操作。
您尚未向我們展示如何將文件分解為List<byte>
的List<byte>
,所以我將假設您有一個類似於IObservable<List<byte>> DeconstructFile(FileInfo fileInfo)
。
現在您可以執行以下操作:
IObservable<string[]> query =
from bytes in DeconstructFile(new FileInfo("myFile.bin"))
from line_str in Observable.Start(() => DecodeBytes(bytes))
select line_str.Split(separator, StringSplitOptions.None);
IDisposable subscription =
query
.ObserveOn(Form1.ActiveForm)
.Subscribe(el => MyDataTable.Rows.Add(el));
而已。 它與Observable.Start
並行運行。根據需要開始啟動新線程,並將結果自動傳遞到每個步驟。 .ObserveOn(Form1.ActiveForm)
自動將.Subscribe
編組到UI線程。
如果需要在代碼完成之前停止代碼,只需調用subscription.Dispose()
。 簡單。
創建多線程應用程序時,必須非常小心,以防止不同的線程同時訪問共享資源。 如果您不阻止它,那么壞事就會發生。 您正在丟失更新,數據結構已損壞,所有這些情況都無法預料地和不一致地發生。 為避免這些問題,您應該同步所有對來自不同線程的共享資源的訪問。 這可以通過使用lock
語句來實現。 因此建議是:在讀取和更新共享資源時始終鎖定。 您的共享資源是Queue
。 您應該這樣鎖定:
// Reading thread
lock (q)
{
q.Enqueue(line_list);
}
// Processing thread
while (true)
{
List<byte> list;
lock (q)
{
if (q.Count == 0) break;
list = new List<byte>(q.Dequeue());
}
string line_str = DecodeBytes(list); // Decoding
// ...
鎖定的缺點是它會引起爭用,因此鎖定不應超過絕對必要。 尤其要避免在持有鎖的同時進行大量計算。
除此之外,您要實現的模式是生產者-消費者模式,.NET提供了專門的類來簡化此模式。 它是BlockingCollection
類,它為您處理所有這些混亂的線程同步。 它可以幫助您減少必須編寫的代碼,但需要花一點時間才能完成。 基本上,您需要學習Add
, CompleteAdding
和GetConsumingEnumerable
,並且已經准備就緒。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.