Как использовать Threadpool.QueueUserWorkItem в службе Windows?
У меня есть служба Windows, где я использую Threadpool.QueueUserWorkItem. Служба подключается к нескольким клиентским базам данных, захватывает данные, конвертирует их в XLS и отправляет файлы на соответствующий FTP.
У меня есть 3 вопроса относительно кода ниже:
- Правильно ли я использую Threadpool.QueueUserWorkItem?
- Нужно ли использовать блокировку в любом месте кода, чтобы избежать проблем? Если да, то где и к какому объекту.
- Есть ли что-то, что не правильно в коде? Если да, что и как с этим делать?
Код:
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.
}
Служба работает нормально, и я получаю соответствующие результаты, но мне нужно убедиться, что я делаю это правильно и не столкнуться с проблемами позже.
1 ответ
Я предполагаю, что ваш сервис предназначен для продолжения работы, а не для того, чтобы быть "одним делом". Если это так, пожалуйста, обратите внимание, что AutoReset
собственность System.Timers.Timer
класс установлен в true
по умолчанию. Это просто означает, что таймер будет продолжать поднимать Elapsed
событие каждый раз, когда проходит 50-секундный интервал (50000 миллисекунд = 50 секунд). Если вы точно знаете , что все 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
, сигнал, что они закончены. В этот момент вы сбрасываете таймер и повторяете следующий интервал.
Извините, это так долго. Надеюсь, поможет.