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