NibblyPig
NibblyPig

Reputation: 52932

What is the current correct way to start an infinite processing loop in a windows service?

I've been researching this for a while but it seems things have changed over the years and there are some conflicting answers.

The way I would traditionally have done it is, in my OnStart method,

new Thread(new ThreadStart(QueueProcessorDoWork));

So that it kicks off the thread then immediately returns.

And then:

private void QueueProcessorDoWork()
        {
            while (_isRunning)
            {
                DoStuff();
                Thread.Sleep(100);
            }
        }

There is no way to make the DoStuff() a blocking operation, it has to basically poll.

Now, Thread.Sleep is considered bad practice because it locks up the thread.

So research has showed me that I can use a timer. No problem with that, I would create a timer, and set it to poll every 100ms, and in the timer handler, I would disable the timer, do the processing, re-enable the timer again, and we'd be all good.

However I then read about using async/await with Task.Delay(100).

That sounds good. I would still need to create a Thread, I believe, because otherwise the OnStart method wouldn't return. However this is unclear, if I call an async method without awaiting it, I don't know what would happen.

So question 1 is - should I just call an async Task that contains the infinite loop without awaiting it to start my windows service?

If I do want to use async/await within a thread, how exactly do I go about getting started? My ThreadStart can call a function that in turn does Task.Run(async () => Worker()) or something, I am not sure since the caller would need to be marked async?

So question 2 is, how would I set up the async/await pattern within a call to new Thread()?

And the final question is, which (or both?) of the two method are the 'correct' way to start an infinite processing loop?

Upvotes: 0

Views: 889

Answers (1)

Stephen Cleary
Stephen Cleary

Reputation: 456407

Now, Thread.Sleep is considered bad practice because it locks up the thread.

Yes and no. Thread.Sleep is not ideal, but if you know this code is always running on a desktop machine, then one extra thread for the lifetime of the process is pretty cheap. I wouldn't lose sleep over it.

So research has showed me that I can use a timer.

Yes, a timer is a fine solution.

However I then read about using async/await with Task.Delay(100).

Sure, you can do this, too. Internally, Task.Delay just starts a timer and finishes the task when the timer fires. So it's practically the same thing.

I would still need to create a Thread, I believe, because otherwise the OnStart method wouldn't return. However this is unclear, if I call an async method without awaiting it, I don't know what would happen.

As soon as an await needs to (asynchronously) wait for an operation to complete, the async method returns. So if you change your loop to await Task.Delay first and then DoStuff, a separate thread would not be necessary. Alternatively, you could just call QueueProcessorDoWork wrapped in a Task.Run, which will queue it to the thread pool.

So question 1 is - should I just call an async Task that contains the infinite loop without awaiting it to start my windows service?

That is one possibility:

var task = Task.Run(() => QueueProcessorDoWork());

Note that task will complete with an exception if the loop throws an exception. I recommend having a "management" method that awaits that task and exits the service if it ever completes:

async Task MainAsync()
{
  try { QueueProcessorDoWork(); }
  catch (Exception ex) { /* log */ }
  finally { /* stop the Win32 service */ }
}

So question 2 is, how would I set up the async/await pattern within a call to new Thread()?

You don't. Thread.Sleep is somewhat discouraged, but new Thread is most definitely discouraged. Unless you're using COM interop, you shouldn't create manual threads.

And the final question is, which (or both?) of the two method are the 'correct' way to start an infinite processing loop?

Just do what works for you. Either a timer or Task.Run wrapping a loop with Task.Delay would work fine.

Upvotes: 4

Related Questions