I have this code :
ManualResetEvent EventListenerStopped;
...
while (true)
{
IAsyncResult iar = this.ListenerHttp.BeginGetContext(ProcessRequest, null);
if (WaitHandle.WaitAny(new[] { this.EventListenerStopped, iar.AsyncWaitHandle }) == 0)
return;
}
Basically it waits for any of two events :
if a request is received, it processes it and wait for the next one.
if EventListenerStopped is raised, it exits the loop.
This code has been running in production beautifully for quite some time now.
I wanted to try and convert it to the new await/async mechanism and can't seem to find a good simple way to do it.
I tried with a boolean the caller can turn to false. It obviously does not work as it exits the loop only after a new request has been received and processed :
bool RunLoop;
...
while (this.RunLoop)
{
HttpListenerContext listenerContext = await this.ListenerHttp.GetContextAsync();
ProcessRequest(listenerContext);
}
I'm wondering if it's even possible to rewrite my simple old-style loop with async/await. If yes, would someone be willing to show me how ?
It's not specific to async-await, but you're probably looking for CancellationToken (which is used with a lot of async-await code anyway):
http://blogs.msdn.com/b/pfxteam/archive/2009/05/22/9635790.aspx
The 'BlockingOperation' example code seems similar to what you're trying to do:
void BlockingOperation(CancellationToken token)
{
ManualResetEvent mre = new ManualResetEvent(false);
//register a callback that will set the MRE
CancellationTokenRegistration registration =
token.Register(() => mre.Set());
using (registration)
{
mre.WaitOne();
if (token.IsCancellationRequested) //did cancellation wake us?
throw new OperationCanceledException(token);
} //dispose the registration, which performs the deregisteration.
}
Well, first I must point out that the old code is not quite correct. When dealing with the Begin/End pattern, you must always call End, even if you want to (or did) cancel the operation. End is often used to dispose resources.
If you do want to use cancellation, a CancellationToken is likely the best approach:
while (true)
{
// Throws an OperationCanceledException when cancellationToken is canceled.
var request = await this.ListenerHttp.GetContextAsync(cancellationToken);
ProcessRequest(request);
}
There are alternatives - it's possible to do something like Task.WhenAny, and there are even implementations of AsyncManualResetEvent, so it's possible to create an almost line-by-line equivalent to the old code, but IMO the cancellation token approach would be cleaner.
For example, using AsyncManualResetEvent from my AsyncEx library:
AsyncManualResetEvent eventListenerStopped;
while (true)
{
var task = GetContextAndProcessRequestAsync();
if (await Task.WhenAny(eventListenerStopped.WaitAsync(), task) != task)
return;
}
async Task GetContextAndProcessRequestAsync()
{
var request = await this.ListenerHttp.GetContextAsync();
ProcessRequest(request);
}
But personally, I would change to use CancellationToken.
Related
I'm currently reading in data via a SerialPort connection in an asynchronous Task in a console application that will theoretically run forever (always picking up new serial data as it comes in).
I have a separate Task that is responsible for pulling that serial data out of a HashSet type that gets populated from my "producer" task above and then it makes an API request with it. Since the "producer" will run forever, I need the "consumer" task to run forever as well to process it.
Here's a contrived example:
TagItems = new HashSet<Tag>();
Sem = new SemaphoreSlim(1, 1);
SerialPort = new SerialPort("COM3", 115200, Parity.None, 8, StopBits.One);
// serialport settings...
try
{
var producer = StartProducerAsync(cancellationToken);
var consumer = StartConsumerAsync(cancellationToken);
await producer; // this feels weird
await consumer; // this feels weird
}
catch (Exception e)
{
Console.WriteLine(e); // when I manually throw an error in the consumer, this never triggers for some reason
}
Here's the producer / consumer methods:
private async Task StartProducerAsync(CancellationToken cancellationToken)
{
using var reader = new StreamReader(SerialPort.BaseStream);
while (SerialPort.IsOpen)
{
var readData = await reader.ReadLineAsync()
.WaitAsync(cancellationToken)
.ConfigureAwait(false);
var tag = new Tag {Data = readData};
await Sem.WaitAsync(cancellationToken);
TagItems.Add(tag);
Sem.Release();
await Task.Delay(100, cancellationToken);
}
reader.Close();
}
private async Task StartConsumerAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
await Sem.WaitAsync(cancellationToken);
if (TagItems.Any())
{
foreach (var item in TagItems)
{
await SendTagAsync(tag, cancellationToken);
}
}
Sem.Release();
await Task.Delay(1000, cancellationToken);
}
}
I think there are multiple problems with my solution but I'm not quite sure how to make it better. For instance, I want my "data" to be unique so I'm using a HashSet, but that data type isn't concurrent-friendly so I'm having to lock with a SemaphoreSlim which I'm guessing could present performance issues with large amounts of data flowing through.
I'm also not sure why my catch block never triggers when an exception is thrown in my StartConsumerAsync method.
Finally, are there better / more modern patterns I can be using to solve this same problem in a better way? I noticed that Channels might be an option but a lot of producer/consumer examples I've seen start with a producer having a fixed number of items that it has to "produce", whereas in my example the producer needs to stay alive forever and potentially produces infinitely.
First things first, starting multiple asynchronous operations and awaiting them one by one is wrong:
// Wrong
await producer;
await consumer;
The reason is that if the first operation fails, the second operation will become fire-and-forget. And allowing tasks to escape your supervision and continue running unattended, can only contribute to your program's instability. Nothing good can come out from that.
// Correct
await Task.WhenAll(producer, consumer)
Now regarding your main issue, which is how to make sure that a failure in one task will cause the timely completion of the other task. My suggestion is to hook the failure of each task with the cancellation of a CancellationTokenSource. In addition, both tasks should watch the associated CancellationToken, and complete cooperatively as soon as possible after they receive a cancellation signal.
var cts = new CancellationTokenSource();
Task producer = StartProducerAsync(cts.Token).OnErrorCancel(cts);
Task consumer = StartConsumerAsync(cts.Token).OnErrorCancel(cts);
await Task.WhenAll(producer, consumer)
Here is the OnErrorCancel extension method:
public static Task OnErrorCancel(this Task task, CancellationTokenSource cts)
{
return task.ContinueWith(t =>
{
if (t.IsFaulted) cts.Cancel();
return t;
}, default, TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default).Unwrap();
}
Instead of doing this, you can also just add an all-enclosing try/catch block inside each task, and call cts.Cancel() in the catch.
Sometimes I need to start an async job which works very slow. I don't care if that job success and I need to continue working on my current thread.
Like sometimes I need to send an Email or SMS which works very slow. I need to respond to the web client as soon as possible so I don't want to await it.
I have googled this question and some articles suggest me to write like this:
// This method has to be async
public async Task<Response> SomeHTTPAction()
{
// Some logic...
// ...
// Send an Email but don't care if it successfully sent.
Task.Run(() => _emailService.SendEmailAsync());
return MyRespond();
}
Or like this:
// This method has to be async
public async Task<Response> SomeHTTPAction()
{
// Some logic...
// ...
// Send an Email but don't care if it successfully sent.
Task.Factory.StartNew(() => _emailService.SendEmailAsync());
return MyRespond();
}
There will be a warning says: before the call is completed. Consider applying the 'await' operator to the result of the call.
So what if I really awaited it? What is the best practice in C# to 'fire and forget', just call an async method without waiting for its completion?
A standalone discard is the best way to avoid this warning.
_ = Task.Run(() => _emailService.SendEmailAsync());
Discards are dummy variables and can be used to ignore the Task object returned by an asynchronous operation.
https://learn.microsoft.com/en-us/dotnet/csharp/discards#a-standalone-discard
If you truly just want to fire and forget. Simply don't call use await.
// It is a good idea to add CancellationTokens
var asyncProcedure = SomeHTTPAction(cancellationToken).ConfigureAwait(false);
// Or If not simply do:
var asyncProcedure = SomeHTTPAction().ConfigureAwait(false);
If you want to use the result output later its gets trickier. But if it is truly fire and forget the above should work
A Cancellation token allows interrupts and canceling procedures. If you are using Cancellation token you will need to use it everywhere from the retrieval straight through to the calling method (Turtles all the way down).
I used ConfigureAwait(false) to prevent deadlocks. Here for more information
EDIT
See the second answer that uses 'Task.Factory.StartNew' I gave this answer some time ago. At the time I didn't realise that the way I did it at the time doesn't ensure completion.
If you need to use async in your function you can also use a discard variable and don't use await. This is also usefull if you have multiple async function calls but you don't need to wait for all of them.
public async function(){
var tmp = await asyncfunction();
...
_ = _httpClient.PutAsync(url, content);
...
}
As Amadan told in the comment that, you need to remove async from your function. then it will stop giving you the warning.
// This method has to be async
public Response SomeHTTPAction()
{
// Some logic...
// ...
// Send an Email but don't care if it successfully sent.
Task.Factory.StartNew(() => _emailService.SendEmailAsync());
return MyRespond();
}
and Task.Factory.StartNew(() => _emailService.SendEmailAsync()); will indeed work on a new thread.
It all depends on what your Async method accepts. Normally it will accept a "special" class that also holds an event. You can subscribe your callback method to that event and pass it along with the method. When it's finished, your callback method will be called.
An example of this (for sockets) would be:
public void CreateSocket()
{
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
SocketAsyncEventArgs sockAsync = new SocketAsyncEventArgs();
sockAsync.Completed += SockAsync_Completed;
s.ConnectAsync(sockAsync);
}
private void SockAsync_Completed(object sender, SocketAsyncEventArgs e)
{
//Do stuff with your callback object.
}
It all depends on what the method you are trying to call can accept. I would look at the documentation for more help on that specifically.
I am curious why this hasn't been suggested.
new Thread(() =>
{
Thread.CurrentThread.IsBackground = true;
//what ever code here...e.g.
DoSomething();
UpdateSomething();
}).Start();
It just fires off a separate thread.
In our application we work a lot with async / await and Tasks. Therefore it does use Task.Run a lot, sometimes with cancellation support using the built in CancellationToken.
public Task DoSomethingAsync(CancellationToken cancellationToken)
{
return Task.Run(() =>
{
while (true)
{
if (cancellationToken.IsCancellationRequested) break;
//do some work
}
}, cancellationToken);
}
If i do now cancel the execution using the CancellationToken the execution does stop at the beginning of the next loop, or if the Task did not start at all it throws an exception (TaskCanceledException inside Task.Run). The question is now why does Task.Run use an Exception to control successful cancellation instead of just returning a completed Task. Is there any specific reason MS did not stick to the "Do NOT use exceptions to control execution flow" rule?.
And how can i avoid Boxing every method that supports cancellation (which are a lot) in an completely useless try catch (TaskCancelledException) block?
Well, you can't really see the difference in your very simple scenario - you're not actually using the result of the Task, and you don't need to propagate the cancellation through a complex call stack.
First, your Task might return a value. What do you return when the operation was cancelled?
Second, there may be other tasks that follow your cancelled task. You probably want to propagate the cancellation through the other tasks at your convenience.
Exceptions propagate. Task cancellation is pretty much identical to Thread.Abort in this usage - when you issue a Thread.Abort, a ThreadAbortException is used to make sure you unwind all the way back to the top. Otherwise, all of your methods would have to check the result of every method they call, check if they were cancelled, and return themselves if needed - and we've already seen that people will ignore error return values in old-school C :)
In the end, task cancellation, just like thread aborts, is an exceptional scenario. It already involves synchronization, stack unwinding etc.
However, this doesn't mean you necessarily have to use try-catch to catch the exception - you can use task states. For example, you can use a helper function like this:
public static Task<T> DefaultIfCanceled<T>(this Task<T> #this, T defaultValue = default(T))
{
return
#this.ContinueWith
(
t =>
{
if (t.IsCanceled) return defaultValue;
return t.Result;
}
);
}
Which you can use as
await SomeAsync().DefaultIfCanceled();
Of course, it should be noted that noöne is forcing you to use this method of cancellation - it's simply provided as a convenience. For example, you could use your own amplified type to preserve the cancellation information, and handle the cancellation manually. But when you start doing that, you'll find the reason why cancellation is handled using exceptions - doing this in imperative code is a pain, so you'll either waste a lot of effort for no gain, or you'll switch to a more functional way of programming (come, we have cookies!*).
(*) Disclaimer: We don't actually have cookies. But you can make your own!
The exception is thrown for a purpose as others in the community have already pointed it out.
However, if you would like to have more control over TaskCanceledException behaviour and still have the logic isolated to one place you may implement an Extension method to extend Task which handles cancellation, something like this -
public async Task DoSomethingAsync(CancellationToken cancellationToken)
{
await Task.Run(() =>
{
while (true)
{
if (cancellationToken.IsCancellationRequested) break;
//do some work
}
}).
WithCancellation(cancellationToken,false); // pass the cancellation token to extension funciton instead to run
}
static class TaskCacellationHelper
{
private struct Void { } // just to support TaskCompletionSource class.
public static async Task WithCancellation(this Task originalTask, CancellationToken ct, bool suppressCancellationExcetion)
{
// Create a Task that completes when the CancellationToken is canceled
var cancelTask = new TaskCompletionSource<Void>();
// When the CancellationToken is canceled, complete the Task
using (ct.Register(
t => ((TaskCompletionSource<Void>)t).TrySetResult(new Void()), cancelTask))
{
// Create a Task that completes when either the original or
// CancellationToken Task completes
Task any = await Task.WhenAny(originalTask, cancelTask.Task);
// If any Task completes due to CancellationToken, throw OperationCanceledException
if (any == cancelTask.Task)
{
//
if (suppressCancellationExcetion == false)
{
ct.ThrowIfCancellationRequested();
}
else
{
Console.WriteLine("Cancelled but exception supressed");
}
}
}
// await original task. Incase of cancellation your logic will break the while loop
await originalTask;
}
}
In our application we work a lot with async / await and Tasks. Therefore it does use Task.Run a lot, sometimes with cancellation support using the built in CancellationToken.
public Task DoSomethingAsync(CancellationToken cancellationToken)
{
return Task.Run(() =>
{
while (true)
{
if (cancellationToken.IsCancellationRequested) break;
//do some work
}
}, cancellationToken);
}
If i do now cancel the execution using the CancellationToken the execution does stop at the beginning of the next loop, or if the Task did not start at all it throws an exception (TaskCanceledException inside Task.Run). The question is now why does Task.Run use an Exception to control successful cancellation instead of just returning a completed Task. Is there any specific reason MS did not stick to the "Do NOT use exceptions to control execution flow" rule?.
And how can i avoid Boxing every method that supports cancellation (which are a lot) in an completely useless try catch (TaskCancelledException) block?
Well, you can't really see the difference in your very simple scenario - you're not actually using the result of the Task, and you don't need to propagate the cancellation through a complex call stack.
First, your Task might return a value. What do you return when the operation was cancelled?
Second, there may be other tasks that follow your cancelled task. You probably want to propagate the cancellation through the other tasks at your convenience.
Exceptions propagate. Task cancellation is pretty much identical to Thread.Abort in this usage - when you issue a Thread.Abort, a ThreadAbortException is used to make sure you unwind all the way back to the top. Otherwise, all of your methods would have to check the result of every method they call, check if they were cancelled, and return themselves if needed - and we've already seen that people will ignore error return values in old-school C :)
In the end, task cancellation, just like thread aborts, is an exceptional scenario. It already involves synchronization, stack unwinding etc.
However, this doesn't mean you necessarily have to use try-catch to catch the exception - you can use task states. For example, you can use a helper function like this:
public static Task<T> DefaultIfCanceled<T>(this Task<T> #this, T defaultValue = default(T))
{
return
#this.ContinueWith
(
t =>
{
if (t.IsCanceled) return defaultValue;
return t.Result;
}
);
}
Which you can use as
await SomeAsync().DefaultIfCanceled();
Of course, it should be noted that noöne is forcing you to use this method of cancellation - it's simply provided as a convenience. For example, you could use your own amplified type to preserve the cancellation information, and handle the cancellation manually. But when you start doing that, you'll find the reason why cancellation is handled using exceptions - doing this in imperative code is a pain, so you'll either waste a lot of effort for no gain, or you'll switch to a more functional way of programming (come, we have cookies!*).
(*) Disclaimer: We don't actually have cookies. But you can make your own!
The exception is thrown for a purpose as others in the community have already pointed it out.
However, if you would like to have more control over TaskCanceledException behaviour and still have the logic isolated to one place you may implement an Extension method to extend Task which handles cancellation, something like this -
public async Task DoSomethingAsync(CancellationToken cancellationToken)
{
await Task.Run(() =>
{
while (true)
{
if (cancellationToken.IsCancellationRequested) break;
//do some work
}
}).
WithCancellation(cancellationToken,false); // pass the cancellation token to extension funciton instead to run
}
static class TaskCacellationHelper
{
private struct Void { } // just to support TaskCompletionSource class.
public static async Task WithCancellation(this Task originalTask, CancellationToken ct, bool suppressCancellationExcetion)
{
// Create a Task that completes when the CancellationToken is canceled
var cancelTask = new TaskCompletionSource<Void>();
// When the CancellationToken is canceled, complete the Task
using (ct.Register(
t => ((TaskCompletionSource<Void>)t).TrySetResult(new Void()), cancelTask))
{
// Create a Task that completes when either the original or
// CancellationToken Task completes
Task any = await Task.WhenAny(originalTask, cancelTask.Task);
// If any Task completes due to CancellationToken, throw OperationCanceledException
if (any == cancelTask.Task)
{
//
if (suppressCancellationExcetion == false)
{
ct.ThrowIfCancellationRequested();
}
else
{
Console.WriteLine("Cancelled but exception supressed");
}
}
}
// await original task. Incase of cancellation your logic will break the while loop
await originalTask;
}
}
So, my app needs to perform an action almost continuously (with a pause of 10 seconds or so between each run) for as long as the app is running or a cancellation is requested. The work it needs to do has the possibility of taking up to 30 seconds.
Is it better to use a System.Timers.Timer and use AutoReset to make sure it doesn't perform the action before the previous "tick" has completed.
Or should I use a general Task in LongRunning mode with a cancellation token, and have a regular infinite while loop inside it calling the action doing the work with a 10 second Thread.Sleep between calls? As for the async/await model, I'm not sure it would be appropriate here as I don't have any return values from the work.
CancellationTokenSource wtoken;
Task task;
void StopWork()
{
wtoken.Cancel();
try
{
task.Wait();
} catch(AggregateException) { }
}
void StartWork()
{
wtoken = new CancellationTokenSource();
task = Task.Factory.StartNew(() =>
{
while (true)
{
wtoken.Token.ThrowIfCancellationRequested();
DoWork();
Thread.Sleep(10000);
}
}, wtoken, TaskCreationOptions.LongRunning);
}
void DoWork()
{
// Some work that takes up to 30 seconds but isn't returning anything.
}
or just use a simple timer while using its AutoReset property, and call .Stop() to cancel it?
I'd use TPL Dataflow for this (since you're using .NET 4.5 and it uses Task internally). You can easily create an ActionBlock<TInput> which posts items to itself after it's processed it's action and waited an appropriate amount of time.
First, create a factory that will create your never-ending task:
ITargetBlock<DateTimeOffset> CreateNeverEndingTask(
Action<DateTimeOffset> action, CancellationToken cancellationToken)
{
// Validate parameters.
if (action == null) throw new ArgumentNullException("action");
// Declare the block variable, it needs to be captured.
ActionBlock<DateTimeOffset> block = null;
// Create the block, it will call itself, so
// you need to separate the declaration and
// the assignment.
// Async so you can wait easily when the
// delay comes.
block = new ActionBlock<DateTimeOffset>(async now => {
// Perform the action.
action(now);
// Wait.
await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken).
// Doing this here because synchronization context more than
// likely *doesn't* need to be captured for the continuation
// here. As a matter of fact, that would be downright
// dangerous.
ConfigureAwait(false);
// Post the action back to the block.
block.Post(DateTimeOffset.Now);
}, new ExecutionDataflowBlockOptions {
CancellationToken = cancellationToken
});
// Return the block.
return block;
}
I've chosen the ActionBlock<TInput> to take a DateTimeOffset structure; you have to pass a type parameter, and it might as well pass some useful state (you can change the nature of the state, if you want).
Also, note that the ActionBlock<TInput> by default processes only one item at a time, so you're guaranteed that only one action will be processed (meaning, you won't have to deal with reentrancy when it calls the Post extension method back on itself).
I've also passed the CancellationToken structure to both the constructor of the ActionBlock<TInput> and to the Task.Delay method call; if the process is cancelled, the cancellation will take place at the first possible opportunity.
From there, it's an easy refactoring of your code to store the ITargetBlock<DateTimeoffset> interface implemented by ActionBlock<TInput> (this is the higher-level abstraction representing blocks that are consumers, and you want to be able to trigger the consumption through a call to the Post extension method):
CancellationTokenSource wtoken;
ActionBlock<DateTimeOffset> task;
Your StartWork method:
void StartWork()
{
// Create the token source.
wtoken = new CancellationTokenSource();
// Set the task.
task = CreateNeverEndingTask(now => DoWork(), wtoken.Token);
// Start the task. Post the time.
task.Post(DateTimeOffset.Now);
}
And then your StopWork method:
void StopWork()
{
// CancellationTokenSource implements IDisposable.
using (wtoken)
{
// Cancel. This will cancel the task.
wtoken.Cancel();
}
// Set everything to null, since the references
// are on the class level and keeping them around
// is holding onto invalid state.
wtoken = null;
task = null;
}
Why would you want to use TPL Dataflow here? A few reasons:
Separation of concerns
The CreateNeverEndingTask method is now a factory that creates your "service" so to speak. You control when it starts and stops, and it's completely self-contained. You don't have to interweave state control of the timer with other aspects of your code. You simply create the block, start it, and stop it when you're done.
More efficient use of threads/tasks/resources
The default scheduler for the blocks in TPL data flow is the same for a Task, which is the thread pool. By using the ActionBlock<TInput> to process your action, as well as a call to Task.Delay, you're yielding control of the thread that you were using when you're not actually doing anything. Granted, this actually leads to some overhead when you spawn up the new Task that will process the continuation, but that should be small, considering you aren't processing this in a tight loop (you're waiting ten seconds between invocations).
If the DoWork function actually can be made awaitable (namely, in that it returns a Task), then you can (possibly) optimize this even more by tweaking the factory method above to take a Func<DateTimeOffset, CancellationToken, Task> instead of an Action<DateTimeOffset>, like so:
ITargetBlock<DateTimeOffset> CreateNeverEndingTask(
Func<DateTimeOffset, CancellationToken, Task> action,
CancellationToken cancellationToken)
{
// Validate parameters.
if (action == null) throw new ArgumentNullException("action");
// Declare the block variable, it needs to be captured.
ActionBlock<DateTimeOffset> block = null;
// Create the block, it will call itself, so
// you need to separate the declaration and
// the assignment.
// Async so you can wait easily when the
// delay comes.
block = new ActionBlock<DateTimeOffset>(async now => {
// Perform the action. Wait on the result.
await action(now, cancellationToken).
// Doing this here because synchronization context more than
// likely *doesn't* need to be captured for the continuation
// here. As a matter of fact, that would be downright
// dangerous.
ConfigureAwait(false);
// Wait.
await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken).
// Same as above.
ConfigureAwait(false);
// Post the action back to the block.
block.Post(DateTimeOffset.Now);
}, new ExecutionDataflowBlockOptions {
CancellationToken = cancellationToken
});
// Return the block.
return block;
}
Of course, it would be good practice to weave the CancellationToken through to your method (if it accepts one), which is done here.
That means you would then have a DoWorkAsync method with the following signature:
Task DoWorkAsync(CancellationToken cancellationToken);
You'd have to change (only slightly, and you're not bleeding out separation of concerns here) the StartWork method to account for the new signature passed to the CreateNeverEndingTask method, like so:
void StartWork()
{
// Create the token source.
wtoken = new CancellationTokenSource();
// Set the task.
task = CreateNeverEndingTask((now, ct) => DoWorkAsync(ct), wtoken.Token);
// Start the task. Post the time.
task.Post(DateTimeOffset.Now, wtoken.Token);
}
I find the new Task-based interface to be very simple for doing things like this - even easier than using the Timer class.
There are some small adjustments you can make to your example. Instead of:
task = Task.Factory.StartNew(() =>
{
while (true)
{
wtoken.Token.ThrowIfCancellationRequested();
DoWork();
Thread.Sleep(10000);
}
}, wtoken, TaskCreationOptions.LongRunning);
You can do this:
task = Task.Run(async () => // <- marked async
{
while (true)
{
DoWork();
await Task.Delay(10000, wtoken.Token); // <- await with cancellation
}
}, wtoken.Token);
This way the cancellation will happen instantaneously if inside the Task.Delay, rather than having to wait for the Thread.Sleep to finish.
Also, using Task.Delay over Thread.Sleep means you aren't tying up a thread doing nothing for the duration of the sleep.
If you're able, you can also make DoWork() accept a cancellation token, and the cancellation will be much more responsive.
Here is what I came up with:
Inherit from NeverEndingTask and override the ExecutionCore method with the work you want to do.
Changing ExecutionLoopDelayMs allows you to adjust the time between loops e.g. if you wanted to use a backoff algorithm.
Start/Stop provide a synchronous interface to start/stop task.
LongRunning means you will get one dedicated thread per NeverEndingTask.
This class does not allocate memory in a loop unlike the ActionBlock based solution above.
The code below is sketch, not necessarily production code :)
:
public abstract class NeverEndingTask
{
// Using a CTS allows NeverEndingTask to "cancel itself"
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
protected NeverEndingTask()
{
TheNeverEndingTask = new Task(
() =>
{
// Wait to see if we get cancelled...
while (!_cts.Token.WaitHandle.WaitOne(ExecutionLoopDelayMs))
{
// Otherwise execute our code...
ExecutionCore(_cts.Token);
}
// If we were cancelled, use the idiomatic way to terminate task
_cts.Token.ThrowIfCancellationRequested();
},
_cts.Token,
TaskCreationOptions.DenyChildAttach | TaskCreationOptions.LongRunning);
// Do not forget to observe faulted tasks - for NeverEndingTask faults are probably never desirable
TheNeverEndingTask.ContinueWith(x =>
{
Trace.TraceError(x.Exception.InnerException.Message);
// Log/Fire Events etc.
}, TaskContinuationOptions.OnlyOnFaulted);
}
protected readonly int ExecutionLoopDelayMs = 0;
protected Task TheNeverEndingTask;
public void Start()
{
// Should throw if you try to start twice...
TheNeverEndingTask.Start();
}
protected abstract void ExecutionCore(CancellationToken cancellationToken);
public void Stop()
{
// This code should be reentrant...
_cts.Cancel();
TheNeverEndingTask.Wait();
}
}