C# 任务并行库-任务工厂行为
我有一个大的(>1GB)文本文件。我需要以多线程方式逐行处理该文件(应用业务逻辑),因此我编写了下一个代码:C# 任务并行库-任务工厂行为,c#,multithreading,asynchronous,task-parallel-library,large-files,C#,Multithreading,Asynchronous,Task Parallel Library,Large Files,我有一个大的(>1GB)文本文件。我需要以多线程方式逐行处理该文件(应用业务逻辑),因此我编写了下一个代码: public Task Parse(Stream content, Action<Trade> parseCallback) { return Task.Factory.StartNew(() => { using (var streamReader = new StreamReader(content)) {
public Task Parse(Stream content, Action<Trade> parseCallback)
{
return Task.Factory.StartNew(() =>
{
using (var streamReader = new StreamReader(content))
{
string line;
while ((line = streamReader.ReadLine()) != null)
{
if (String.IsNullOrWhiteSpace(line))
{
continue;
}
var tokens = line.Split(TokensSeparator);
if (!tokens.Any() || tokens.Count() != 6)
{
continue;
}
Task.Factory.StartNew(() => parseCallback(new Trade
{
Id = Int32.Parse(tokens[0]),
MktPrice = Decimal.Parse(tokens[1], CultureInfo.InvariantCulture),
Notional = Decimal.Parse(tokens[2], CultureInfo.InvariantCulture),
Quantity = Int64.Parse(tokens[3]),
TradeDate = DateTime.Parse(tokens[4], CultureInfo.InvariantCulture),
TradeType = tokens[5]
}),
TaskCreationOptions.AttachedToParent);
}
}
});
}
问题是:
2+3:如果你启动第二个线程,让它创建任务,UI就不会被阻塞。 不过,您不必这样做——您的主线程可以创建一个任务列表,并等待所有任务(Task.WhenAll)。正如您所说,在循环中创建任务非常快,UI将仅在创建任务所需的时间内被阻止 编辑: 我只是意识到你根本不使用异步,这使得我的答案无关紧要。为什么不使用异步从磁盘读取?您可以异步地从磁盘读取大量数据(这是程序中必须耗时的部分,不是吗?),并在数据到达时进行处理 编辑2: 这听起来像是一个经典的生产者-消费者场景(我希望我是对的)。请查看以下示例:您有一个线程(主线程,虽然不一定是)异步读取文件中的行,并将它们推送到队列中。另一个线程,消费者,在用户到达时提取行并处理它们。我没有测试代码,我也不希望它工作得很好,这只是一个开始的例子。希望能有帮助
class ProducerConsumer
{
private BlockingCollection<string> collection;
ICollection<Thread> consumers;
string fileName;
public ProducerConsumer(string fileName)
{
this.fileName = fileName;
collection = new BlockingCollection<string>();
consumers = new List<Thread>();
var consumer = new Thread(() => Consumer());
consumers.Add(consumer);
consumer.Start();
}
private async Task StartWork()
{
using (TextReader reader = File.OpenText(fileName))
{
var line = await reader.ReadLineAsync();
collection.Add(line);
}
}
private void Consumer()
{
while (true /* insert your abort condition here*/)
{
try
{
var line = collection.Take();
// Do whatever you need with this line. If proccsing this line takes longer then
// fetching the next line (that is - the queue lenght increasing too fast) - you
// can always launch an additional consumer thread.
}
catch (InvalidOperationException) { }
}
}
}
class ProducerConsumer
{
私人封锁收集;
i收集消费者;
字符串文件名;
公共ProducerConsumer(字符串文件名)
{
this.fileName=文件名;
collection=newblockingcollection();
消费者=新列表();
var consumer=新线程(()=>consumer());
消费者。添加(消费者);
consumer.Start();
}
专用异步任务StartWork()
{
使用(TextReader=File.OpenText(文件名))
{
var line=wait reader.ReadLineAsync();
集合。添加(行);
}
}
私人用户()
{
while(true/*在此处插入中止条件*/)
{
尝试
{
var line=collection.Take();
//用这条线做任何你需要的。如果处理这条线需要更长的时间
//获取下一行(即队列长度增长过快)-您
//始终可以启动其他使用者线程。
}
捕获(无效操作异常){}
}
}
}
您可以启动一个专用线程(而不是主线程)作为生产者。因此,它将读取文件并尽可能快地将项目添加到队列中,您的磁盘也可以。如果这对你的消费者来说太快了-只需再启动一个 您可以通过应用信号量来控制线程 如果需要,它将运行最多320个线程,然后等待前面的线程完成
public class Utitlity
{
public static SemaphoreSlim semaphore = new SemaphoreSlim(300, 320);
public static char[] TokensSeparator = "|,".ToCharArray();
public async Task Parse(Stream content, Action<Trade> parseCallback)
{
await Task.Run(async () =>
{
using (var streamReader = new StreamReader(content))
{
string line;
while ((line = streamReader.ReadLine()) != null)
{
if (String.IsNullOrWhiteSpace(line))
{
continue;
}
var tokens = line.Split(TokensSeparator);
if (!tokens.Any() || tokens.Count() != 6)
{
continue;
}
await semaphore.WaitAsync();
await Task.Run(() =>
{
var trade = new Trade
{
Id = Int32.Parse(tokens[0]),
MktPrice = Decimal.Parse(tokens[1], CultureInfo.InvariantCulture),
Notional = Decimal.Parse(tokens[2], CultureInfo.InvariantCulture),
Quantity = Int64.Parse(tokens[3]),
TradeDate = DateTime.Parse(tokens[4], CultureInfo.InvariantCulture),
TradeType = tokens[5]
};
parseCallback(trade);
});
semaphore.Release();
}
}
});
}
}
public class Trade
{
public int Id { get; set; }
public decimal MktPrice { get; set; }
public decimal Notional { get; set; }
public long Quantity { get; set; }
public DateTime TradeDate { get; set; }
public string TradeType { get; set; }
}
公共类实用性
{
公共静态信号量slim信号量=新信号量slim(300320);
公共静态字符[]令牌分离器=“|,”.tocharray();
公共异步任务解析(流内容、动作解析回调)
{
等待任务。运行(异步()=>
{
使用(var streamReader=新streamReader(内容))
{
弦线;
而((line=streamReader.ReadLine())!=null)
{
if(String.IsNullOrWhiteSpace(行))
{
继续;
}
var tokens=行分割(TokensSeparator);
如果(!tokens.Any()| | tokens.Count()!=6)
{
继续;
}
wait semaphore.WaitAsync();
等待任务。运行(()=>
{
var贸易=新贸易
{
Id=Int32.Parse(令牌[0]),
MktPrice=Decimal.Parse(标记[1],CultureInfo.InvariantCulture),
概念=Decimal.Parse(标记[2],CultureInfo.InvariantCulture),
Quantity=Int64.Parse(标记[3]),
TradeDate=DateTime.Parse(标记[4],CultureInfo.InvariantCulture),
TradeType=代币[5]
};
商业(贸易);;
});
semaphore.Release();
}
}
});
}
}
公营贸易
{
公共int Id{get;set;}
公共十进制MktPrice{get;set;}
公共十进制实数{get;set;}
公共长数量{get;set;}
public DateTime TradeDate{get;set;}
公共字符串类型{get;set;}
}
更改解析
,以便它返回一个惰性的IEnumerable
行。事实上,您可以使用内置的文件。EnumerateLines
来删除大部分代码
然后,使用PLINQ查询:<
public class Utitlity
{
public static SemaphoreSlim semaphore = new SemaphoreSlim(300, 320);
public static char[] TokensSeparator = "|,".ToCharArray();
public async Task Parse(Stream content, Action<Trade> parseCallback)
{
await Task.Run(async () =>
{
using (var streamReader = new StreamReader(content))
{
string line;
while ((line = streamReader.ReadLine()) != null)
{
if (String.IsNullOrWhiteSpace(line))
{
continue;
}
var tokens = line.Split(TokensSeparator);
if (!tokens.Any() || tokens.Count() != 6)
{
continue;
}
await semaphore.WaitAsync();
await Task.Run(() =>
{
var trade = new Trade
{
Id = Int32.Parse(tokens[0]),
MktPrice = Decimal.Parse(tokens[1], CultureInfo.InvariantCulture),
Notional = Decimal.Parse(tokens[2], CultureInfo.InvariantCulture),
Quantity = Int64.Parse(tokens[3]),
TradeDate = DateTime.Parse(tokens[4], CultureInfo.InvariantCulture),
TradeType = tokens[5]
};
parseCallback(trade);
});
semaphore.Release();
}
}
});
}
}
public class Trade
{
public int Id { get; set; }
public decimal MktPrice { get; set; }
public decimal Notional { get; set; }
public long Quantity { get; set; }
public DateTime TradeDate { get; set; }
public string TradeType { get; set; }
}
File.EnumerateLines(path)
.AsParallel()
.Where(x => !String.IsNullOrWhiteSpace(line))
.Select(line => ProcessLine(line);