我有大(> 1 Gb)的文本文件。我需要以多线程的方式逐行处理该文件(应用业务逻辑),所以我编写了下一个代码:
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);
}
}
});
}
其中 Action parseCallback 对从数据行创建的数据对象应用业务逻辑。
Parse()方法返回Task,调用者线程等待父任务完成:
try
{
var parseTask = parser.Parse(fileStream, AddTradeToTradeResult);
parseTask.Wait();
}
catch (AggregateException ae)
{
throw new ApplicationException(ae.Flatten().InnerException.Message, ae);
}
问题是:
答案 0 :(得分:0)
2 + 3:如果启动第二个线程,并让它创建任务,则UI不会被阻止。 但是,您不必这样做 - 您的主线程可以创建任务列表,并等待它们全部(Task.WhenAll)。正如您所说,在循环中创建任务非常快,并且UI将仅在创建任务所花费的时间内被阻止。
编辑:
我只是意识到你根本不使用异步,这使我的回答无关紧要。为什么不使用异步从磁盘读取?您可以异步读取磁盘中的大量数据(这是您必须耗费时间的一部分程序,不是吗?)并在它们到达时对它们进行处理。
EDIT2:
这听起来像是一个典型的制片人 - 消费者场景(我希望我是对的)。请查看以下示例:您有一个线程(主线程,认为它不一定是)从文件中异步读取行,并将它们推送到队列中。另一个线程,即消费者,在到达并处理它们时拾取线条。我没有测试代码,我认为它不能很好地工作,这只是一个开始的例子。希望它有所帮助。
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) { }
}
}
}
您可以启动专用线程 - 而不是主线程 - 作为生产者。因此,它将读取文件并以尽可能快的速度将项目添加到队列中,并且您的磁盘可以。如果这对您的消费者来说太快了 - 只需再推出一个!
答案 1 :(得分:0)
您可以通过应用信号量来控制线程 如果需要,它将运行最多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; }
}
答案 2 :(得分:0)
更改Parse
以便它返回一行懒惰的IEnumerable<string>
行。实际上,您可以使用内置File.EnumerateLines
来删除大多数代码。
然后,使用PLINQ查询:
File.EnumerateLines(path)
.AsParallel()
.Where(x => !String.IsNullOrWhiteSpace(line))
.Select(line => ProcessLine(line);
就是这样。这将以更加平和的程度运行。确切的DOP由TPL选择。该算法很不稳定,您可能需要添加WithDegreeOfParallelism(ProcessorCount)
。
如果您愿意,可以通过附加parseCallback
来致电.ForAll(parseCallback)
。
在大文件处理期间我是否可以与控制台应用程序窗口进行交互,否则它将被阻止?
为了做到这一点,你需要在Task.Run
中包装这个PLINQ查询,以便它在后台线程上执行。