簡體   English   中英

使用隊列在兩個BackgroundWorkers之間傳遞數據

[英]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類,它為您處理所有這些混亂的線程同步。 它可以幫助您減少必須編寫的代碼,但需要花一點時間才能完成。 基本上,您需要學習AddCompleteAddingGetConsumingEnumerable ,並且已經准備就緒。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM