2

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

我对以下代码有 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.
        }

该服务运行良好,我得到了适当的结果,但我需要确保我做对了,以后不会遇到问题。

4

1 回答 1

5

我假设您的服务旨在继续运行,而不是“一次性完成”。如果是这样,请注意该类的AutoReset属性默认设置为。这仅仅意味着每次经过 50 秒间隔(50000 毫秒 = 50 秒)时,计时器将继续引发事件。如果您确定在下一个间隔过去之前所有 操作都在足够长的时间内完成,那么您应该没问题。但是,我不会打赌。如果数据库在网络上并且网络出现故障怎么办?如果服务在较慢的系统或内核较少的系统上运行并且所有工作都没有及时完成怎么办?System.Timers.TimertrueElapsedSendFilesToClient

您可以通过AutoReset像这样关闭该功能来解决此问题。

private static var aTimer = new System.Timers.Timer(50000) { AutoReset = false };

这意味着该Elapsed事件只会触发一次。在 内部PerformTimerOperation,只需将Enabled属性重置为true以在退出之前重新启动计时器。

但这是一个不完整的解决方案,因为在计时器触发另一个Elapsed事件之前线程可能仍然需要很长时间才能完成。在这种情况下,您可能希望ManualResetEvent在每个线程完成时使用 a 发出信号并暂停退出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发出它们已完成的信号。此时,您重置计时器并在下一个间隔重复。

对不起,这么久。希望能帮助到你。

于 2013-02-26T19:25:57.450 回答