我正在自学有关Parallel.Invoke和一般的并行处理,以便在当前项目中使用。我需要向正确的方向努力,以了解如何根据需要动态\智能地分配更多并行“线程”。
举个例子。假设您正在解析大型日志文件。这涉及从文件中读取,对返回的行进行某种解析,最后写入数据库。
所以对我而言,这是一个可以从并行处理中受益的典型问题。
作为简单的第一遍,以下代码实现了这一点。
Parallel.Invoke(
()=> readFileLinesToBuffer(),
()=> parseFileLinesFromBuffer(),
()=> updateResultsToDatabase()
);
幕后花絮
所以显示的代码假定这三个步骤中的每一个都使用相同的时间\资源,但是假设parseFileLinesFromBuffer()是一个长时间运行的进程,所以不要只运行其中一个方法而是要并行运行两个
如何让代码根据它可能感知到的任何瓶颈,智能地决定执行此操作?
从概念上讲,我可以看到一些监视缓冲区大小的方法是如何工作的,产生一个新的“线程”来以更高的速率使用缓冲区,例如...但我认为这种类型的问题已被考虑在一起TPL库。
一些示例代码会很棒,但我真的只需要了解我接下来要调查的概念。看起来可能是System.Threading.Tasks.TaskScheduler持有密钥?
答案 0 :(得分:4)
您是否尝试过Reactive Extensions?
http://msdn.microsoft.com/en-us/data/gg577609.aspx
Rx是微软的一项新技术,正如官方网站所述:
Reactive Extensions(Rx)......是一个要编写的库 使用可观察集合的异步和基于事件的程序 LINQ样式的查询运算符。
您可以将其下载为Nuget包
https://nuget.org/packages/Rx-Main/1.0.11226
由于我目前正在学习Rx,我想采用这个例子并为它编写代码,我最终得到的代码实际上并不是并行执行,而是完全异步,并保证源代码行按顺序执行
也许这不是最好的实现,但就像我说我正在学习Rx,(线程安全应该是一个很好的改进)
这是我用来从后台线程返回数据的DTO
class MyItem
{
public string Line { get; set; }
public int CurrentThread { get; set; }
}
这些是完成实际工作的基本方法,我用简单的Thread.Sleep
模拟时间,并且我返回用于执行每个方法Thread.CurrentThread.ManagedThreadId
的线程。注意ProcessLine
的计时器是4秒,这是最耗时的操作
private IEnumerable<MyItem> ReadLinesFromFile(string fileName)
{
var source = from e in Enumerable.Range(1, 10)
let v = e.ToString()
select v;
foreach (var item in source)
{
Thread.Sleep(1000);
yield return new MyItem { CurrentThread = Thread.CurrentThread.ManagedThreadId, Line = item };
}
}
private MyItem UpdateResultToDatabase(string processedLine)
{
Thread.Sleep(700);
return new MyItem { Line = "s" + processedLine, CurrentThread = Thread.CurrentThread.ManagedThreadId };
}
private MyItem ProcessLine(string line)
{
Thread.Sleep(4000);
return new MyItem { Line = "p" + line, CurrentThread = Thread.CurrentThread.ManagedThreadId };
}
以下方法我只是用它来更新UI
private void DisplayResults(MyItem myItem, Color color, string message)
{
this.listView1.Items.Add(
new ListViewItem(
new[]
{
message,
myItem.Line ,
myItem.CurrentThread.ToString(),
Thread.CurrentThread.ManagedThreadId.ToString()
}
)
{
ForeColor = color
}
);
}
最后这是调用Rx API的方法
private void PlayWithRx()
{
// we init the observavble with the lines read from the file
var source = this.ReadLinesFromFile("some file").ToObservable(Scheduler.TaskPool);
source.ObserveOn(this).Subscribe(x =>
{
// for each line read, we update the UI
this.DisplayResults(x, Color.Red, "Read");
// for each line read, we subscribe the line to the ProcessLine method
var process = Observable.Start(() => this.ProcessLine(x.Line), Scheduler.TaskPool)
.ObserveOn(this).Subscribe(c =>
{
// for each line processed, we update the UI
this.DisplayResults(c, Color.Blue, "Processed");
// for each line processed we subscribe to the final process the UpdateResultToDatabase method
// finally, we update the UI when the line processed has been saved to the database
var persist = Observable.Start(() => this.UpdateResultToDatabase(c.Line), Scheduler.TaskPool)
.ObserveOn(this).Subscribe(z => this.DisplayResults(z, Color.Black, "Saved"));
});
});
}
此过程完全在后台运行,这是生成的输出:
答案 1 :(得分:0)
在async / await世界中,你有类似的东西:
public async Task ProcessFileAsync(string filename)
{
var lines = await ReadLinesFromFileAsync(filename);
var parsed = await ParseLinesAsync(lines);
await UpdateDatabaseAsync(parsed);
}
然后调用者可以执行var tasks = filenames.Select(ProcessFileAsync).ToArray();等等(WaitAll,WhenAll等,取决于具体情况)
答案 2 :(得分:0)
使用几个BlockingCollection
。 Here is an example
您的想法是创建一个producer
,将数据放入集合
while (true) {
var data = ReadData();
blockingCollection1.Add(data);
}
然后您创建从集合中读取的任意数量的使用者
while (true) {
var data = blockingCollection1.Take();
var processedData = ProcessData(data);
blockingCollection2.Add(processedData);
}
等等
您还可以让TPL使用Parallel.Foreach
处理使用者数量Parallel.ForEach(blockingCollection1.GetConsumingPartitioner(),
data => {
var processedData = ProcessData(data);
blockingCollection2.Add(processedData);
});
(请注意,您需要使用GetConsumingPartitioner
而不是GetConsumingEnumerable
(see here)