19

I have a Windows Service that uses Thread and SemaphoreSlim to perform some "work" every 60 seconds.

class Daemon
{
    private SemaphoreSlim _semaphore;
    private Thread _thread;

    public void Stop()
    {
        _semaphore.Release();
        _thread.Join();
    }

    public void Start()
    {
        _semaphore = new SemaphoreSlim(0);
        _thread = new Thread(DoWork);
        _thread.Start();
    }

    private void DoWork()
    {
        while (true)
        {
            // Do some work here

            // Wait for 60 seconds, or exit if the Semaphore is released
            if (_semaphore.Wait(60 * 1000))                
            {
                return;
            }
        }
    }
}

I'd like to call an asynchronous method from DoWork. In order to use the await keyword I must add async to DoWork:

private async void DoWork()
  1. Is there any reason not to do this?
  2. Is DoWork actually able to run asynchronously, if it's already running inside a dedicated thread?
James
  • 2,404
  • 2
  • 28
  • 33
  • It might make more sense to make `DoWork` async and not start new thread for it, but that depends on what exactly `DoWork` is doing and why you need to make it async. – Evk Jun 05 '17 at 08:00
  • `async` anonymous methods in `Task.Run()` is a common enough pattern, and it's functionally equivalent to what you're asking. Your question is primarily opinion based, and yes there are reasons not to do it, but probably not any really good ones. Do note that, just as in the `Task.Run()` scenario, your thread will exit as soon as the first `await` is reached. – Peter Duniho Jun 05 '17 at 08:00
  • 1
    See possible duplicate https://stackoverflow.com/questions/34808035/starting-async-method-as-thread-or-as-task for discussion regarding these types of scenarios and the wisdom or lack thereof. Your question lacks detail, making it also broad, but if it were me, I'd refactor both the thread and the semaphore to be TPL-based. There's nothing in the bit of info you posted that suggests a dedicated thread or semaphore is warranted anyway. – Peter Duniho Jun 05 '17 at 08:08
  • Part of the problem, at least in my understanding, is the weird way that services run. You are usually performing some sort of sleep/work/sleep/.. cycle, but also need to be able to respond to a "Stop" command, to allow the service to exit. If anyone has any links to a TPL style service, please post them! – James Jun 05 '17 at 08:13
  • In the context of a service, "stop" translates to `CancellationTokenSource`, which works very well with the TPL API. – Peter Duniho Jun 05 '17 at 08:16
  • To expand on what I'm trying to find out here... If I add `async` to `DoWork()` the code still works. But I don't know async/await well enough to understand what's actually happening with that dedicated thread. The use of `await` means that the method exits, and will continue later, correct? But if that's happening, what prevents the Thread from exiting? – James Jun 05 '17 at 08:17
  • 1
    Just found this, which gives a nice explanation: https://stackoverflow.com/questions/27383996/basic-design-pattern-for-using-tpl-inside-windows-service-for-c-sharp – James Jun 05 '17 at 08:25
  • 1
    @PeterDuniho the first *not synchronously completed* `await`; pedantic point, perhaps, but ... sometimes it matters – Marc Gravell Jun 05 '17 at 08:50

1 Answers1

14

You can do this, but it wouldn't be a good idea. As soon as the first await hits that is not synchronously completed, the rest of the work will be done on a continuation, not the thread you started (_thread); the thread you start will terminate at the first such await. There is no guarantee that a continuation will go back to the originating thread, and in your scenario, it cannot - that thread is now toast. That means that:

  1. _thread is meaningless and does not represent the state of the operation; as such, your Stop() method with a _thread.Join(); doesn't do what you expect it to do
  2. you've created a thread (allocating threads is expensive, in particular because of the size of the stack) only to have it exit almost immediately

Both of these issues can avoided by using Task.Run to start such operations.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Thank you. I think I will reimplement the service using the TPL pattern I found here: https://stackoverflow.com/questions/27383996/basic-design-pattern-for-using-tpl-inside-windows-service-for-c-sharp – James Jun 05 '17 at 08:52
  • 2
    Not that it would be a good idea for the OP, but is there a best practice for starting a thread using an async method? Neither the `Thread` constructor, nor `Task.Factory.StartNew` have an overload that accepts a `Func` and `Task.Run` creates a thread-pool thread. Should I just do `void Foo() => FooAsync().Wait(); async Task FooAsync() { ... }` and use `Foo` as the `ThreadStart`? – Şafak Gür Sep 07 '19 at 16:12
  • The downside of the `Task.Run` is it immediately starts running and the it might be needed to start running threads at a later time. – Kamran Nov 05 '21 at 18:38
  • 2
    @Kamran you can always just create the delegate now, and call Task.Run later... – Marc Gravell Nov 05 '21 at 21:40
  • "the thread you start will terminate at the first such await" Where can I read more about this? – o4zloiroman Aug 23 '22 at 12:12
  • 2
    @o4zloiroman that's simply a direct consequence of how a) manual threads, and b) async operations - work; a thread started via `Thread` (rather than a pool thread) exists to invoke the delegate passed to it, and then terminate; an `async void` method only run synchronously as far as the first incomplete async await (this is also true of `async Task` etc, but with `async Task`, the incomplete state is at least visible to the caller). So: the moment an `async void` method hits an incomplete await: the thread you spawned for it: unrolls and dies. When the await completes: it'll happen elsewhere. – Marc Gravell Aug 24 '22 at 09:42