0

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?

NibblyPig
  • 51,118
  • 72
  • 200
  • 356
  • 1
    Stick with a timer. – DavidG Jan 31 '20 at 09:40
  • Possible duplicate of [CPU friendly infinite loop](https://stackoverflow.com/a/12367882/1838048) – Oliver Jan 31 '20 at 09:49
  • The `OnStart` method must return immediately after setting up the processing loop as it's a windows service. The duplicate, like many examples, creates the timer and then sits there in an infinite loop. But in my case, I must return from OnStart immediately. The setting up of the timer I guess should be on a new thread. I am not sure if async/await should be involved in that. – NibblyPig Jan 31 '20 at 09:50
  • You can fire and forget a Task the same as you can fire and forget a Thread. Task.Run() will run on another thread, one from the default worker pool. Then you can Thread.Sleep() etc. But if you only have one thread and you have to sleep (or ``Task.Delay()``) and you don;t want it to do more than one thing at a time in that thread then I can't see any benefit from Tasks. – Rowan Smith Jan 31 '20 at 10:00
  • There is a lot of very useful content inside this post which might help: https://stackoverflow.com/questions/37000403/synchronous-i-o-within-an-async-await-based-windows-service – Rowan Smith Jan 31 '20 at 10:06
  • Timer does not need a new thread. – DavidG Jan 31 '20 at 10:08
  • 1
    Related follow-up question: [How do I ThreadStart a method that is labelled async?](https://stackoverflow.com/questions/60002701/how-do-i-threadstart-a-method-that-is-labelled-async) – Theodor Zoulias Jan 31 '20 at 13:37

1 Answers1

3

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.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Thanks that was super helpful, especially about the management part. It seems then that a windows service would run with the OnStart method making a call to an async Task (not awaited) and then return, so windows would be happy that the service started OK. Then the processing could occur. If you do wrap that in a try catch, even though the function has kinda exited, would it still trigger the catch? Presumably there's a closure over the stack when you do Task.Run without awaiting it – NibblyPig Jan 31 '20 at 14:04
  • @NibblyPig: Exceptions from `async` methods are placed on their `Task`. When the `Task` is `await`ed, the exception is rethrown. So you can catch an exception by placing the `await` within a `try`/`catch`. There's a closure over local stack variables, but nothing further up the stack. – Stephen Cleary Jan 31 '20 at 14:06
  • Ah so I'd definitely have to put my Task.Run inside another Task.Run to stop it blocking the OnStart method, the first Task.Run would simply wait for the long running task to complete (and do nothing) or error (log the exception). Many thanks. – NibblyPig Jan 31 '20 at 14:17
  • @NibblyPig: The outer "management" one doesn't need another `Task.Run`. It just needs an `await`. – Stephen Cleary Jan 31 '20 at 14:39
  • I think await would prevent the method from exiting, which is required for a windows service OnStart method to work though – NibblyPig Jan 31 '20 at 14:45
  • 1
    @NibblyPig: Read the link I posted. When an `await` waits for an operation, the method returns. – Stephen Cleary Jan 31 '20 at 15:20
  • Yes, it returns from the async method, but it doesn't exit the method that called it otherwise it would continue running lines of code. OnStart method must finish for a windows service to launch, so if you put an await in then it will block – NibblyPig Jan 31 '20 at 15:32
  • 1
    @NibblyPig: A method with an `await` in it must itself be an `async` method. And when the `await` waits, that `async` method will return. This is true for all methods, including `OnStart`. – Stephen Cleary Jan 31 '20 at 15:56