我有一个windows服务,我正在使用Threadpool.QueueUserWorkItem。该服务连接到多个客户端数据库,获取数据,转换为XLS,并将文件发送到相应的FTP。
关于下面的代码,我有3个问题:
- 我是否正确使用了Threadpool.QueueUserWorkItem
- 我是否需要在代码中的任何位置使用Lock来避免问题?如果是,在哪里以及指向什么对象
- 代码中有什么不正确的地方吗?如果是,如何处理
代码:
private static System.Timers.Timer aTimer = new System.Timers.Timer(50000);
public void OnStart(string[] args)
{
CLE.WriteToEventLog("Service Started");
try
{
aTimer.Elapsed += new ElapsedEventHandler(PerformTimerOperation);
aTimer.Enabled = true;
}
catch (Exception ex)
{
CLE.WriteToEventLog("Error Starting Service: " + ex.Message);
}
}
private void PerformTimerOperation(object source, ElapsedEventArgs e)
{
CLE.WriteToEventLog("Timer Operation Started");
Clients objClient = new Clients();
List<Clients> objClientList = Clients.GetClientList();
foreach (var list in objClientList)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(SendFilesToClient), list);
}
}
private void SendFilesToClient(Object stateInfo)
{
CLE.WriteToEventLog("Send Files To Client Started");
Clients oClient = (Clients)stateInfo;
CLE.WriteToEventLog("Start Proecessing Client: " + oClient.ClientName + ", ClientId: " + oClient.ClientId);
connectionString = App.Database.PrimaryConnectionString(oClient.ClientId);
string reports = oClient.Reports;
string[] values = reports.Split(',').Select(sValue => sValue.Trim()).ToArray();
foreach (string item in values)
{
//Send data to FTP based on cliend id
}
// At this point all reports are being sent to the FTP. We will update the database with LastExecutionDateTime + 1 hour. This will be used as DateFrom param for all reports for the next execution.
}
这项服务运行良好,我得到了适当的结果,但我需要确保我做得正确,以后不会遇到问题。
我假设您的服务是为了保持运行,而不是"一次性完成"。如果是,请注意System.Timers.Timer
类的AutoReset
属性默认设置为true
。这只是意味着,每当经过50秒的间隔(50000毫秒=50秒(时,计时器将继续引发Elapsed
事件。如果您确信所有SendFilesToClient
操作在下一个间隔之前都会在足够的时间内完成,那么您应该没事。但是,我不会打赌。如果数据库在网络上,而网络出现故障怎么办?如果服务运行在速度较慢的系统或内核较少的系统上,并且所有工作都没有及时完成,该怎么办?
你可以像这样关闭AutoReset
功能来解决这个问题
private static var aTimer = new System.Timers.Timer(50000) { AutoReset = false };
这意味着Elapsed
事件将只触发一次。在PerformTimerOperation
中,只需将Enabled
属性重置为true
即可在退出前重新启动计时器。
但这是一个不完整的解决方案,因为在计时器触发另一个Elapsed
事件之前,线程可能仍然需要很长时间才能完成。在这种情况下,您可能希望使用ManualResetEvent
在每个线程完成时发出信号,并挂起退出PerformTimerOperation
(并重置计时器(,直到出现这种情况。例如,
private void PerformTimerOperation(object source, ElapsedEventArgs e)
{
List<Clients> objClientList = new Clients().GetClientList();
List<ManualResetEvent> handles = new List<ManualResetEvent();
foreach (var list in objClientList)
{
// Create an MRE for each thread.
var handle = ManualResetEvent(false);
// Store it for use below.
handles.Add(handle);
// Notice two things:
// 1. Using new WaitCallback(...) syntax is not necessary.
// 2. Thread argument is now a Tuple object.
ThreadPool.QueueUserWorkItem(SendFilesToClient, Tuple.Create(list, handle));
}
// Wait for threads to finish.
WaitHandle.WaitAll(handles.ToArray());
// Reset the timer.
aTimer.Enabled = true;
}
现在更新SendFilesToClient
。
private void SendFilesToClient(Object stateInfo)
{
// The parameter is now a Tuple<T1, T2>, not a Clients object.
var tuple = (Tuple<Clients, ManualResetEvent>)stateInfo;
try
{
Clients oClient = tuple.Item1;
// Do your work here...
}
catch (Exception ex)
{
// Handle any exception here.
}
finally
{
// Signal that the work is done...even if an exception occurred.
// Otherwise, PerformTimerOperation() will block forever.
ManualResetEvent mreEvent = tuple.Item2;
mreEvent.Set();
}
}
以这种方式,PerformTimerOperation
将阻塞WaitHandle.WaitAll()
调用,直到所有工作线程(例如SendFilesToClient
(发出它们完成的信号。此时,您将重置计时器,并在下一个间隔中重复。
对不起,时间太长了。希望能有所帮助。