简体   繁体   中英

Task Handling on Application Shutdown

I have a .Net (v4.0) Windows Service Application that spins of a tpl task at the beginning that performs certain, long running activities and basically stays alive for the application's life time and as such is created with the TaskCreationOptions. LongRunning parameter value.

Whenever the service is stopped and the .OnStop() method is called, I .Cancel() the CancellationToken(Source) I handed over to the worker task when I created it and I want it's .OnlyOnCanceled(...) continuation task to run.

The thing is, the service/process shuts down without that continuation task to run through "entirely" - sometimes it quits rather quickly, sometimes it runs through completely, sometimes it doesn't.

This does make sense to me as that particular task is probably sitting on another thread than the main one and thereby has no way of "stalling"/blocking that main one to end.

As I do not have a SynchronizationContext in that windows service application I can't tell that continuation task to run there/on the main thread so I was wondering: how would I do that?

And more precisely, what's the best practice to handle an application shutdown with running tpl tasks?

You'll have to wait for your task to complete in the OnStop method (or OnPause and OnShutdown ).

You have about 20 seconds to do whatever you need in OnStop . If you don't think your thread will complete in 20 seconds, you should call RequestAdditionalTime . As soon as you return from OnStop your service process can be terminated.

Using ContinueWith will asynchronously invoke the delegate passed to it, regardless of whether you pass ExecuteSynchronously or use a SynchronizationContext . As soon as the ContinueWith executes, assuming that's the last line of OnStop , OnStop return and returns control to the SCN (well, ServiceBase , but it sets your service's state to STOPPED and returns control to the SCM to presumably terminate your process.

ExecuteSynchronously means that the continuation runs synchronously with regard to the task it's continuing from. ie run on the same thread as the task (if possible). The task is likely not running on the thread that is calling ContinueWith (otherwise it couldn't call ContinueWith ) so ExecuteSynchronusly doesn't mean synchronously with regard to the call to ContinueWith .

You need to do something like:

RequestAdditionalTime(TimeSpan.FromSeconds(30).Milliseconds);
cancellationToken.Cancel();
task.Wait();

in OnStop , the Wait means you won't exit from OnStop until your task completes (or it takes longer than 30 seconds and your process gets terminated)

This happens so because the continuation task also runs asynchronously . In order for it to block you need to specify task continuation option :

...
t.ContinueWith(ct => {...}, TaskContinuationOptions.ExecuteSynchronously);

Specifying TaskContinuationOptions.ExecuteSynchronously on the continuation does not influence the antecedent. If you wait on the antecedent, then exit the service, your continuation might still not run!

The only way to do this is to wait for the continuation task to complete before returning from OnStop .

IMHO, this is a bad fit for use of TPL. Tasks aren't really meant for this kind of 'run forever' logic.

IME, it's much simpler/easier to start a new thread for this dedicated work. Since the default is Background = false, the CLR will automatically wait for it to complete before exiting (service rules still apply).

Add a private bool stopping; and then your thread method just needs to do while (stopping == false) { DoStuff(); } DoStoppingStuff();

Your OnStop then just sets stopping = true and your thread will exit the while loop and do your stopping code :)

IME you don't need to add volatile, but you certainly could

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM