我正在逐字节读取和解码二进制文件。为此,我使用了两个BackgroundWorker
:一个用于读取文件,为文件的每个“行”生成一个List<byte>
大小可变的文件,另一个用于处理“行”。>
由于我希望它们并行运行,所以我不知道哪个会比另一个更快,因此我使用Queue
在两个BackgroundWorker
之间传递数据。
这就是问题:List<byte>
随时都不应包含任何0
值。我检查了之前将它们添加到队列中。尽管如此,在Queue
的另一端,某些列表仍包含0
值。但是,我每次调用List<byte>
时都会创建一个新的Dequeue()
,因为显然,如果我不这样做,则数据会在处理完成之前被修改。
我尝试手动创建一个新的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()添加的相同数据
答案 0 :(得分:0)
您应该使用Microsoft的Reactive Framework(又名Rx)-NuGet System.Reactive.Windows.Forms
(假设您正在编写WinForms应用程序)并添加using System.Reactive.Linq;
。
Rx让您使用熟悉的LINQ语法进行并行操作。
您尚未向我们展示如何将文件分解为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()
。很简单。
答案 1 :(得分:0)
创建多线程应用程序时,必须非常小心,以防止不同的线程同时访问共享资源。如果您不阻止它,那么坏事就会发生。您正在丢失更新,数据结构已损坏,所有这些情况都无法预料地和不一致地发生。为避免这些问题,您应该同步所有对来自不同线程的共享资源的访问。这可以通过使用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
,然后就可以开始了。