2

我有一个Windows服务,我正在使用Threadpool.QueueUserWorkItem。该服务连接到多个客户端数据库,获取数据,转换为XLS并将文件发送到相应的FTP。如何在Windows服务中使用Threadpool.QueueUserWorkItem?

我有3个有关代码如下问题:

  1. 我是正确使用Threadpool.QueueUserWorkItem?
  2. 我是否需要在代码中的任何位置使用Lock以避免出现问题?如果是的话,哪里和什么对象。
  3. 代码中是否有任何不正确的内容?如果是的话,该如何处理呢?

代码:

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. 
     } 

服务工作正常,我也得到相应的结果,但我需要确保我这样做是正确的,不以对问题以后运行。

+0

刚刚做了@格雷。这是C#.NET 4.0 – Learner 2013-02-26 17:48:17

+1

这对我来说很好。关于你的问题#2,因为你的线程完全独​​立运行,所以不需要锁定。当您协调对共享资源的访问时,锁定是必要的,但在这种情况下,每个工作线程都有自己的数据库连接,并且正在使用一组不同的“东西”。 – GalacticCowboy 2013-02-26 18:08:31

+0

谢谢@GalacticCowboy! – Learner 2013-02-26 19:40:02

回答

5

我假设你的服务是为了保持运行而不是“一劳永逸”。如果是这样,请注意默认情况下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,表明它们完成。此时,您重置计时器并在下一个时间间隔重复。

对不起,这是如此之久。希望能帮助到你。

+0

根本不是@Matt。这是美妙的解释。此外,仅供参考,此服务需要每1小时运行一次才能将文件发送到FTP。所以我没有看到在完成前面的任务之前服务开始运行的任何问题。这有什么区别吗?我是否应该继续寻求解决方案(实施AutoReset和ManualReset)作为最佳做法?任何输入锁定?谢谢! – Learner 2013-02-26 19:46:15

+1

假设前一个任务仍在工作,并开始一个新任务。你打开数据库连接?您可以打开多少个同时连接可能会受到限制。收件人怎么样?如果他们在第一项任务之前从第二项任务中获得文件,是否会出现问题?这些事件的可能性可能很小,但一个强大的系统处理这些问题。你比我更了解你的系统,所以用你的工程判断。如果“PerformTimerOperation”通常不超过几分钟,我怀疑你没事。 – 2013-02-26 20:14:30