Search code examples
c#windows-servicestask-parallel-librarytopshelf

Why does Task.Run() in windows service OnStart() cause the service to refuse to stop?


I'm not sure why this service is refusing to stop.

I ran into this when trying to correct a TimeoutException thrown when starting the service. Using:

public void OnStart()
{
    _startTask = Task.Run(DoWork, _cancelTokenSource.Token);
}

private void DoWork(){ [listen for things and operate on them] }

public void OnStop()
{
    _cancelTokenSource.Cancel();
    _startTask.Wait();
}

I understand that implementing a simple timer will solve this, but that's not the point of my question. Why does the use of Task.Run(() => action, _tokenSource.Token) resolve the TimeoutException but causes the service to not respond to control messages?

The issue Observed

After installing and starting the service (it's TopShelf BTW), I'm unable to stop the service by conventional methods.

First Attempt:

enter image description here

All Subsequent Attempts:

enter image description here

Edit: Still no joy

Here is my attempt after following the provided example.

public void Start()
{
    var token = _cancelTokenSource.Token;
    Task.Factory.StartNew(() => Setup(token), token);
}

public void Stop()
        {
            _cancelTokenSource.Cancel();
            //TearDown(); <-- original implementation of stop
        }

private void Setup(CancellationToken token)
        {
            _mailman.SendServiceStatusNotification(_buildMode, "Started");
            ... create some needed objects

            token.Register(TearDown);

            InitializeInboxWatcherProcess(...);

        }

private void TearDown()
        {
            _inboxWatcher.Terminate();
            _mailman.SendServiceStatusNotification(_buildMode, "Stopped");
        }

private void InitializeInboxWatcherProcess(...)
        {
            // pre create and initiate stuff
            _inboxWatcher = new LocalFileSystemWatcherWrapper(...);
            _inboxWatcher.Initiate();
        }

public class LocalFileSystemWatcherWrapper : IFileSystemWatcherWrapper
    {
        // do FileSystemWatcher setup and control stuff
    }

Solution

  • This is most likely because you either don't have a cancellation method, or there are subprocesses inside of DoWork() that are still running when you call Cancel(). As @Damien_The_Unbeliever said cancellation is a cooperative task.

    When you call _cancelTokenSource.Cancel() if you haven't registered a callback function all that happens is that a boolean value isCancellationRequested is set to true, the DoWork() method is then responsible for seeing this and stopping its execution on its own. There is a flaw here, though, as you can probably tell, that if you have a time consuming loop running in the DoWork() task when Cancel() is called, that loop will have to finish an iteration before it can check the value of isCancellationRequested which can lead to hanging.

    The way around this is to insert cancellation callback functions INTO the DoWork() method, see here and then register them to the token, so that when you call the Cancel() method, ALL of the tasks running in the background are stopped without having to wait for them.

    Hope this helps!