8
\$\begingroup\$

I need to perform few tasks inside a Windows Service I am writing in parallel. I am using VS2013, .NET 4.5 and this post shows that TPL is the way to go.

I was wondering if anyone can tell me if I have done it correctly.

public partial class FtpLink : ServiceBase
{
    private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
    private readonly ManualResetEvent _runCompleteEvent = new ManualResetEvent(false);

    public FtpLink()
    {
        InitializeComponent();

        // Load configuration
        WebEnvironment.Instance.Initialise();
    }

    protected override void OnStart(string[] args)
    {
        Trace.TraceInformation("DatabaseToFtp is running");

        try
        {
            RunAsync(_cancellationTokenSource.Token).Wait();
        }
        finally
        {
            _runCompleteEvent.Set();
        }
    }

    protected override void OnStop()
    {
        Trace.TraceInformation("DatabaseToFtp is stopping");

        _cancellationTokenSource.Cancel();
        _runCompleteEvent.WaitOne();

        Trace.TraceInformation("DatabaseToFtp has stopped");
    }

    private async Task RunAsync(CancellationToken cancellationToken)
    {
        while (!cancellationToken.IsCancellationRequested)
        {
            Trace.TraceInformation("Working");

            // Do the actual work
            var tasks = new List<Task>
            {
                Task.Factory.StartNew(() => new Processor().ProcessMessageFiles(), cancellationToken),
                Task.Factory.StartNew(() => new Processor().ProcessFirmware(), cancellationToken)
            };

            Task.WaitAll(tasks.ToArray(), cancellationToken);

            // Delay the loop for a certain time
            await Task.Delay(WebEnvironment.Instance.DatabasePollInterval, cancellationToken);
        }
    }
}
\$\endgroup\$
  • 2
    \$\begingroup\$ Seems odd to me that you run something asynchronously and then wait. Seems to be defeating the purpose. The purpose of async is to run something that takes an unknown amount of time to complete, due to depending on something not under your control (often network or disk IO), and being able to still perform other tasks during that time -- not just waiting for it to be done. \$\endgroup\$ – Snowbody Apr 9 '15 at 2:55
  • \$\begingroup\$ @Snowbody, all I want to do is be able to call ProcessMessageFiles and ProcessFirmware methods in parallel and once both are done, sleep for a set time defined by DatabasePollInterval before I call them again. What modifications should I make then? P.S. Sorry about double comments, I get a notification with only 1 user will be notified. \$\endgroup\$ – WRACK Apr 9 '15 at 3:37
  • 1
    \$\begingroup\$ It's not a matter of "modifications"; it's a matter of design philosophy. You seem to be completely misunderstanding the purpose and uses of async; that's not something that can be fixed with modifications. In short: What problem are you trying to solve, and why do you think async solves this problem? Based on what you say, plain ol' threads seem like a better match. \$\endgroup\$ – Snowbody Apr 9 '15 at 4:40
  • \$\begingroup\$ Maybe with an Event that both threads wait on so they both start at the same time. \$\endgroup\$ – Snowbody Apr 9 '15 at 4:41
5
\$\begingroup\$

The general design of your example is correct, however there are some problems with how you have implemented the async code. Most notibly

  • Calling .Wait() on the Task returned by RunAsync() will block 'OnStart()'. Instead it should be done after cancellation in the OnStop() method. This would eliminate the need to use a ManualResetEvent
  • Task.WaitAll() should be replace with await Task.WhenAll()

More importantly you can achieve the desired behaviour much more simply by using a System.Threading.Timer. The TPL is needed only to perform parallel processing.

public partial class FtpLink : ServiceBase
{
    private Timer _timer;

    public FtpLink()
    {
        WebEnvironment.Instance.Initialise();
    }

    protected override void OnStart(string[] args)
    {
        Trace.TraceInformation("DatabaseToFtp service started.");
        _timer = new Timer(Process, null, 0, WebEnvironment.Instance.DatabasePollInterval);
    }

    protected override void OnStop()
    {
        _timer.Dispose();
        Trace.TraceInformation("DatabaseToFtp service stopped.");
    }

    private void Process(object state)
    {
        Trace.TraceInformation("Processing message files and firmware...");

        Parallel.Invoke(
            () => new Processor().ProcessMessageFiles(),
            () => new Processor().ProcessFirmware());

        Trace.TraceInformation("Processing complete.");
    }
}

If you want to ensure that the service waits for processing to complete before exiting, you can use a simple Monitor as shown in the answer to this question.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.