我需要在一夜之间处理大量文件,并定义开始和结束时间以避免中断用户。我一直在调查,但现在处理线程的方法太多了,我不确定要走哪条路。这些文件作为附件进入Exchange收件箱。
我目前的尝试,基于这里的一些例子和一些实验,是:
while (DateTime.Now < dtEndTime.Value)
{
var finished = new CountdownEvent(1);
for (int i = 0; i < numThreads; i++)
{
object state = offset;
finished.AddCount();
ThreadPool.QueueUserWorkItem(delegate
{
try
{
StartProcessing(state);
}
finally
{
finished.Signal();
}
});
offset += numberOfFilesPerPoll;
}
finished.Signal();
finished.Wait();
}
此刻它在winforms应用程序中运行是为了方便,但核心处理是在一个DLL中,所以我可以从一个Windows服务,从一个在调度程序下运行的控制台生成我需要的类,但是最容易的。我确实设置了一个带有Timer对象的Windows服务,该对象在配置文件中设置的时间启动处理。
所以我的问题是 - 在上面的代码中,我初始化了一堆线程(目前是10个),然后等待它们全部处理。我的理想是一个静态的线程数,当一个完成时我触发另一个,然后当我到达结束时间我只是等待所有线程完成。 这样做的原因是我处理的文件是可变大小的 - 有些可能需要几秒钟才能处理,有些可能需要几个小时,所以我不希望整个应用程序在一个线程完成时等待,如果可以的话让它在后台滴答作响。 (编辑)就目前而言,每个线程都实例化一个类并向其传递一个偏移量。然后,该类从收件箱中获取下一个x电子邮件,从偏移量开始(使用Exchange Web服务分页功能)。在处理每个文件时,它会移动到单独的文件夹中。从目前为止的一些回复中,我想知道我是否应该抓住外部循环中的电子邮件,并根据需要生成线程。 为了解决这个问题,我目前有一些积压的电子邮件,我试图通过这些电子邮件处理。一旦积压清单,夜间运行的负载可能会显着降低。
平均每晚要处理大约1000个文件。
更新
我已经重写了大量的代码,以便我可以使用Parallel.Foreach并且我遇到了线程安全问题。调用代码现在看起来像这样:
public bool StartProcessing()
{
FindItemsResults<Item> emails = GetEmails();
var source = new CancellationTokenSource(TimeSpan.FromHours(10));
// Process files in parallel, with a maximum thread count.
var opts = new ParallelOptions { MaxDegreeOfParallelism = 8, CancellationToken = source.Token };
try
{
Parallel.ForEach(emails, opts, processAttachment);
}
catch (OperationCanceledException)
{
Console.WriteLine("Loop was cancelled.");
}
catch (Exception err)
{
WriteToLogFile(err.Message + "\r\n");
WriteToLogFile(err.StackTrace + "r\n");
}
return true;
}
到目前为止一切顺利(借助临时错误处理)。我现在有一个新问题,因为&#34;项目&#34;对象,这是一封电子邮件,不是线程安全的。例如,当我开始处理电子邮件时,我将其移至&#34;处理&#34;文件夹,以便其他进程无法抓住它 - 但事实证明,有几个线程可能正在尝试一次处理同一封电子邮件。我如何保证这不会发生?我知道我需要添加一个锁,我可以在ForEach中添加它还是应该在processAttachments方法中?
答案 0 :(得分:2)
使用TPL:
Parallel.ForEach( EnumerateFiles(),
new ParallelOptions { MaxDegreeOfParallelism = 10 },
file => ProcessFile( file ) );
让EnumerateFiles
停止枚举您的结束时间,如下所示:
IEnumerable<string> EnumerateFiles()
{
foreach (var file in Directory.EnumerateFiles( "*.txt" ))
if (DateTime.Now < _endTime)
yield return file;
else
yield break;
}
答案 1 :(得分:1)
您可以使用Parallel.ForEach()
和取消令牌来源的组合,这将在一段时间后取消操作:
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Demo
{
static class Program
{
static Random rng = new Random();
static void Main()
{
// Simulate having a list of files.
var fileList = Enumerable.Range(1, 100000).Select(i => i.ToString());
// For demo purposes, cancel after a few seconds.
var source = new CancellationTokenSource(TimeSpan.FromSeconds(10));
// Process files in parallel, with a maximum thread count.
var opts = new ParallelOptions {MaxDegreeOfParallelism = 8, CancellationToken = source .Token};
try
{
Parallel.ForEach(fileList, opts, processFile);
}
catch (OperationCanceledException)
{
Console.WriteLine("Loop was cancelled.");
}
}
static void processFile(string file)
{
Console.WriteLine("Processing file: " + file);
// Simulate taking a varying amount of time per file.
int delay;
lock (rng)
{
delay = rng.Next(200, 2000);
}
Thread.Sleep(delay);
Console.WriteLine("Processed file: " + file);
}
}
}
作为使用取消令牌的替代方法,您可以编写一个返回IEnumerable<string>
的方法,该方法返回文件名列表,并在时间结束时停止返回它们,例如:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Demo
{
static class Program
{
static Random rng = new Random();
static void Main()
{
// Process files in parallel, with a maximum thread count.
var opts = new ParallelOptions {MaxDegreeOfParallelism = 8};
Parallel.ForEach(fileList(), opts, processFile);
}
static IEnumerable<string> fileList()
{
// Simulate having a list of files.
var fileList = Enumerable.Range(1, 100000).Select(x => x.ToString()).ToArray();
// Simulate finishing after a few seconds.
DateTime endTime = DateTime.Now + TimeSpan.FromSeconds(10);
int i = 0;
while (DateTime.Now <= endTime)
yield return fileList[i++];
}
static void processFile(string file)
{
Console.WriteLine("Processing file: " + file);
// Simulate taking a varying amount of time per file.
int delay;
lock (rng)
{
delay = rng.Next(200, 2000);
}
Thread.Sleep(delay);
Console.WriteLine("Processed file: " + file);
}
}
}
请注意,您不需要使用此方法进行try / catch。
答案 2 :(得分:0)
您应该考虑使用Microsoft的Reactive Framework。它允许您使用LINQ查询以非常简单的方式处理多线程异步处理。
这样的事情:
var query =
from file in filesToProcess.ToObservable()
where DateTime.Now < stopTime
from result in Observable.Start(() => StartProcessing(file))
select new { file, result };
var subscription =
query.Subscribe(x =>
{
/* handle result */
});
确实,如果StartProcessing
已经定义,那就是您需要的所有代码。
Just NuGet&#34; Rx-Main&#34;。
哦,要随时停止处理,只需致电subscription.Dispose()
。
答案 3 :(得分:0)
这是一项真正令人着迷的任务,我花了一段时间才把代码提升到一个令我满意的程度。
我最终得到了上述的组合。
首先要注意的是,我将以下行添加到我的Web服务调用中,因为我遇到的操作超时,我认为是因为我超过了端点设置的某些限制,实际上是由于微软在.Net 2.0中设置的限制:
ServicePointManager.DefaultConnectionLimit = int.MaxValue;
ServicePointManager.Expect100Continue = false;
有关详细信息,请参阅此处:
What to set ServicePointManager.DefaultConnectionLimit to
只要我添加了这些代码行,我的处理速度就会从10分钟增加到100分钟左右。
但是我仍然对循环和分区等不满意。我的服务转移到物理服务器上以最小化CPU争用,我想让操作系统决定它的运行速度,而不是我的代码限制它
经过一些研究,这就是我最终得到的 - 可能不是我编写的最优雅的代码,但它非常快速和可靠。
List<XElement> elements = new List<XElement>();
while (XMLDoc.ReadToFollowing("ElementName"))
{
using (XmlReader r = XMLDoc.ReadSubtree())
{
r.Read();
XElement node = XElement.Load(r);
//do some processing of the node here...
elements.Add(node);
}
}
//And now pass the list of elements through PLinQ to the actual web service call, allowing the OS/framework to handle the parallelism
int failCount=0; //the method call below sets this per request; we log and continue
failCount = elements.AsParallel()
.Sum(element => IntegrationClass.DoRequest(element.ToString()));
它结果非常简单,闪电般快速。
我希望这有助于其他人尝试做同样的事情!