I have controller, where I create a Cancellation Token in the constructor and use it in a long running await task (). Does disposing the Cancellation token in Controller.Dispose() causes the long running task to cancel?
public class SomeController : BaseInternalController
{
private CancellationTokenSource cancellationTokenSource;
public MyController()
{
cancellationTokenSource = new CancellationTokenSource();
}
public async Task<HttpResponseMessage> Post(SomeData data)
{
foreach (var item in data)
{
await longRunningTask(item, cancellationTokenSource.token);
}
}
protected override void Dispose(bool disposing)
{
if (cancellationTokenSource != null)
{
cancellationTokenSource.Cancel();
cancellationTokenSource.Dispose();
cancellationTokenSource = null;
}
base.Dispose(disposing);
}
}
Does disposing the Cancellation token in Controller.Dispose() causes
the long running task to cancel?
Depends on how your longRunningTask was implemented.
In this method you should explicitly check whether the cancellation is requested:
token.ThrowIfCancellationRequested();
After invoking of this method your task will be cancelled.
Cancellation example
If in the following example the ThrowIfCancellationRequested would not be invoked, the task would be run forever:
var cts = new CancellationTokenSource();
Task.Run(() =>
{
while (true)
cts.Token.ThrowIfCancellationRequested();
}, cts.Token);
You can learn more about cancellation here.
Note that after setting the cancellationTokenSource to null, you can get NullReferenceException in your foreach loop. I would suggest to copy your token into a local variable:
public async Task<HttpResponseMessage> Post(SomeData data)
{
var token = cancellationTokenSource.Token;
foreach (var item in data)
{
await longRunningTask(item, token);
}
}
Related
I have a class which creates a Task which runs during its whole lifetime, that is, until Dispose() is called on it:
In the constructor I call:
_worker = Task.Run(() => ProcessQueueAsync(_workerCancellation.Token), _workerCancellation.Token);
The way I currently do it (which I am also not sure is the right way) is cancelling the CancellationToken, and waiting on the task.
public void Dispose()
{
if (_isDisposed)
{
return;
}
_workerCancellation.Cancel();
_worker.GetAwaiter().GetResult();
_isDisposed = true;
}
When I do the same in the AsyncDispose method like so:
public async ValueTask DisposeAsync()
{
await _worker;
}
I get this warning
How do I correctly dispose of such a worker? Thanks!
As requested, here is the full code of what I am trying to do:
public sealed class ActiveObjectWrapper<T, TS> : IAsyncDisposable
{
private bool _isDisposed = false;
private const int DefaultQueueCapacity = 1024;
private readonly Task _worker;
private readonly CancellationTokenSource _workerCancellation;
private readonly Channel<(T, TaskCompletionSource<TS>)> _taskQueue;
private readonly Func<T, TS> _onReceive;
public ActiveObjectWrapper(Func<T, TS> onReceive, int? queueCapacity = null)
{
_onReceive = onReceive;
_taskQueue = Channel.CreateBounded<(T, TaskCompletionSource<TS>)>(queueCapacity ?? DefaultQueueCapacity);
_workerCancellation = new CancellationTokenSource();
_worker = Task.Run(() => ProcessQueueAsync(_workerCancellation.Token), _workerCancellation.Token);
}
private async Task ProcessQueueAsync(CancellationToken cancellationToken)
{
await foreach (var (value, taskCompletionSource) in _taskQueue.Reader.ReadAllAsync(cancellationToken))
{
try
{
var result = _onReceive(value); // todo: do I need to propagate the cancellation token?
taskCompletionSource.SetResult(result);
}
catch (Exception exception)
{
taskCompletionSource.SetException(exception);
}
}
}
public async Task<TS> EnqueueAsync(T value)
{
// see: https://devblogs.microsoft.com/premier-developer/the-danger-of-taskcompletionsourcet-class/
var completionSource = new TaskCompletionSource<TS>(TaskCreationOptions.RunContinuationsAsynchronously);
await _taskQueue.Writer.WriteAsync((value, completionSource));
return await completionSource.Task;
}
public async ValueTask DisposeAsync()
{
if (_isDisposed)
{
return;
}
_taskQueue.Writer.Complete();
_workerCancellation.Cancel();
await _worker;
_isDisposed = true;
}
}
This is the pattern that I use when implementing both IDisposabe and IDisposeableAsync. It isn't strictly compliant with the .Net recommendations. I found that implementing DisposeAsyncCore() was unnecessary as my classes are sealed.
public void Dispose()
{
Dispose(disposing: true);
//GC.SuppressFinalize(this);
Worker.GetAwaiter().GetResult();
}
public void Dispose(bool disposing)
{
if (isDisposed)
{
return;
}
if (disposing)
{
lock (isDisposing)
{
if (isDisposed)
{
return;
}
Cts.Cancel();
Cts.Dispose();
isDisposed = true;
}
}
}
public async ValueTask DisposeAsync()
{
Dispose(disposing: true);
//GC.SuppressFinalize(this);
await Worker;
}
This looks like an attempt to build a TransformBlock<TIn,TOut> on top of Channels. Using a Channel properly wouldn't generate such a warning. There's no reason to use a single processing task, or store it in a field.
Using Blocks
First, the equivalent code using a TransformBlock<Tin,TOut> would be:
var block=new TransformBlock<TIn,TOut>(msgIn=>func(msgIn));
foreach(....)
{
block.Post(someMessage);
}
To stop it, block.Complete() would be enough. Any pending messages would still be processed. A TransformBlock is meant to forward its output to other blocks, eg an ActionBlock or BufferBlock.
var finalBlock=new ActionBlock<TOut>(msgOut=>Console.WriteLine(msgOut));
block.LinkTo(finalBlock,new DataflowLinkOptions{PropagateCompletion = true});
...
block.Complete();
await finalBlock.Completion;
Using Channels
Doing something similar with channels doesn't need explicit classes, or Enqueue/Dequeue methods. Channels are build with separate ChannelReader, ChannelWriter interfaces to make it easier to control ownership, concurrency and completion.
A similar pipeline using channels would require only some methods:
ChannelReader<string> FolderToChannel(string path,CancellationToken token=default)
{
Channel<int> channel=Channel.CreateUnbounded();
var writer=channel.Writer;
_ = Task.Run(async ()=>{
foreach(var path in Directory.EnumerateFiles(path))
{
await _writer.SendAsync(path);
if (token.CancellationRequested)
{
return;
}
}
},token).ContinueWith(t=>_writer.TryComplete(t.Exception));
return channel;
}
This produces a reader that can be passed to a processing method. One that could generate another reader with the results:
static ChannelReader<MyClass> ParseFile(this ChannelReader<string> reader,CancellationToken token)
{
Channel<int> channel=Channel.CreateUnbounded();
var writer=channel.Writer;
_ = Task.Run(async ()=>{
await foreach(var path in reader.ReadAllAsync(token))
{
var json= await File.ReadAllTextAsync(path);
var dto= JsonSerializer.DeserializeObject<MyClass>(json);
await _writer.SendAsync(dto);
}
},token).ContineWith(t=>writer.TryComplete(t.Exception);
return channel;
}
And a final step that only consumes a channel:
static async Task LogIt(this ChannelReader<MyClass> reader,CancellationToken token)
{
await Task.Run(async ()=>{
await foreach(var dto in reader.ReadAllAsync(token))
{
Console.WriteLine(dto);
}
},token);
}
The three steps can be combined very easily:
var cts=new CancellationTokenSource();
var complete=FolderToChannel(somePath,cts.Token)
.ParseFile(cts.Token)
.LogIt(cts.Token);
await complete;
By encapsulating the channel itself and the processing in a method there's no ambiguity about who owns the channel, who is responsible for completion or cancellation
I have a method that contains an infinite loop. I call that method from main method and I want to cancel it if it takes more that 2 seconds.
I tried these approaches
Task.WhenAny with cancellation of the non completed tasks and timeout
How can I cancel Task.WhenAll?
How to cancel a task using a CancellationToken and await Task.WhenAny
and lots of other links, but non has worked.
here is my code
static void Main(string[] args)
{
var cts = new CancellationTokenSource(2000);
var task = Task.Run(() => RunTask(), cts.Token);
await task;
}
public static void RunTask()
{
while (true)
{
Console.WriteLine("in while");
}
}
I also tried to cancel my token but it didn't work.
It is not possible to Dispose the task.
I don't have access to RunTask source code. I just can run it.
I cannot send CancellationToken to my method. How can I terminate RunTask() method?
Cancellation tokens are a way to request cancellation. It's up to the running code to accept that request and actually stop the work. It's not like killing a process. For example, if you had something like:
public static void RunTask(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
Console.WriteLine("in while");
}
}
Then, when it's canceled, the loop will end. However, given that the current code doesn't offer any method of canceling and you cannot modify it, then there is no way to cancel it.
What about this approach:
static void Main(string[] args)
{
var cts = new CancellationTokenSource(2000);
var task = Task.Run(() => RunTask(cts.Token), cts.Token);
await task;
}
public static void RunTask(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
Console.WriteLine("in while");
}
}
I created a small wrapper around CancellationToken and CancellationTokenSource. The problem I have is that the CancelAsync method of CancellationHelper doesn't work as expected.
I'm experiencing the problem with the ItShouldThrowAExceptionButStallsInstead method. To cancel the running task, it calls await coordinator.CancelAsync();, but the task is not cancelled actually and doesn't throw an exception on task.Wait
ItWorksWellAndThrowsException seems to be working well and it uses coordinator.Cancel, which is not an async method at all.
The question why is the task is not cancelled when I call CancellationTokenSource's Cancel method in async method?
Don't let the waitHandle confuse you, it's only for not letting the task finish early.
Let the code speak for itself:
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace TestCancellation
{
class Program
{
static void Main(string[] args)
{
ItWorksWellAndThrowsException();
//ItShouldThrowAExceptionButStallsInstead();
}
private static void ItShouldThrowAExceptionButStallsInstead()
{
Task.Run(async () =>
{
var coordinator = new CancellationHelper();
var waitHandle = new ManualResetEvent(false);
var task = Task.Run(() =>
{
waitHandle.WaitOne();
//this works well though - it throws
//coordinator.ThrowIfCancellationRequested();
}, coordinator.Token);
await coordinator.CancelAsync();
//waitHandle.Set(); -- with or without this it will throw
task.Wait();
}).Wait();
}
private static void ItWorksWellAndThrowsException()
{
Task.Run(() =>
{
var coordinator = new CancellationHelper();
var waitHandle = new ManualResetEvent(false);
var task = Task.Run(() => { waitHandle.WaitOne(); }, coordinator.Token);
coordinator.Cancel();
task.Wait();
}).Wait();
}
}
public class CancellationHelper
{
private CancellationTokenSource cancellationTokenSource;
private readonly List<Task> tasksToAwait;
public CancellationHelper()
{
cancellationTokenSource = new CancellationTokenSource();
tasksToAwait = new List<Task>();
}
public CancellationToken Token
{
get { return cancellationTokenSource.Token; }
}
public void AwaitOnCancellation(Task task)
{
if (task == null) return;
tasksToAwait.Add(task);
}
public void Reset()
{
tasksToAwait.Clear();
cancellationTokenSource = new CancellationTokenSource();
}
public void ThrowIfCancellationRequested()
{
cancellationTokenSource.Token.ThrowIfCancellationRequested();
}
public void Cancel()
{
cancellationTokenSource.Cancel();
Task.WaitAll(tasksToAwait.ToArray());
}
public async Task CancelAsync()
{
cancellationTokenSource.Cancel();
try
{
await Task.WhenAll(tasksToAwait.ToArray());
}
catch (AggregateException ex)
{
ex.Handle(p => p is OperationCanceledException);
}
}
}
}
Cancellation in .NET is cooperative.
That means that the one holding the CancellationTokenSource signals cancellation and the one holding the CancellationToken needs to check whether cancellation was signaled (either by polling the CancellationToken or by registering a delegate to run when it is signaled).
In your Task.Run you use the CancellationToken as a parameter, but you don't check it inside the task itself so the task will only be cancelled if the token was signaled before the task had to a chance to start.
To cancel the task while it's running you need to check the CancellationToken:
var task = Task.Run(() =>
{
token.ThrowIfCancellationRequested();
}, token);
In your case you block on a ManualResetEvent so you wouldn't be able to check the CancellationToken. You can register a delegate to the CancellationToken that frees up the reset event:
token.Register(() => waitHandle.Set())
I'm implementing a worker engine with an upper limit to concurrency. I'm using a semaphore to wait until concurrency drops below the maximum, then use Task.Factory.StartNew to wrap the async handler in a try/catch, with a finally which releases the semaphore.
I realise this creates threads on the thread pool - but my question is, when one of those task-running threads actually awaits (on a real IO call or wait handle), is the thread returned to the pool, as I'd hope it would be?
If there's a better way to implement a task scheduler with limited concurrency where the work handler is an async method (returns Task), I'd love to hear it too. Or, let's say ideally, if there's a way to queue up an async method (again, it's a Task-returning async method) that feels less dodgy than wrapping it in a synchronous delegate and passing it to Task.Factory.StartNew, that would seem perfect..?
(This also makes me think that there are two kinds of parallelism here: how many tasks are being processed overall, but also how many continuations are running on different threads concurrently. Might be cool to have configurable options for both, though not a fixed requirement..)
Edit: snippet:
concurrencySemaphore.Wait(cancelToken);
deferRelease = false;
try
{
var result = GetWorkItem();
if (result == null)
{ // no work, wait for new work or exit signal
signal = WaitHandle.WaitAny(signals);
continue;
}
deferRelease = true;
tasks.Add(Task.Factory.StartNew(() =>
{
try
{
DoWorkHereAsync(result); // guess I'd think to .GetAwaiter().GetResult() here.. not run this yet
}
finally
{
concurrencySemaphore.Release();
}
}, cancelToken));
}
finally
{
if (!deferRelease)
{
concurrencySemaphore.Release();
}
}
Here an example of a TaskWorker, that will not produce countless worker threads.
The magic is done by awaiting SemaphoreSlim.WaitAsync() which is an IO task (and there is no thread).
class TaskWorker
{
private readonly SemaphoreSlim _semaphore;
public TaskWorker(int maxDegreeOfParallelism)
{
if (maxDegreeOfParallelism <= 0)
{
throw new ArgumentOutOfRangeException(nameof(maxDegreeOfParallelism));
}
_semaphore = new SemaphoreSlim(maxDegreeOfParallelism, maxDegreeOfParallelism);
}
public async Task RunAsync(Func<Task> taskFactory, CancellationToken cancellationToken = default(CancellationToken))
{
// No ConfigureAwait(false) here to keep the SyncContext if any
// for the real task
await _semaphore.WaitAsync(cancellationToken);
try
{
await taskFactory().ConfigureAwait(false);
}
finally
{
_semaphore.Release(1);
}
}
public async Task<T> RunAsync<T>(Func<Task<T>> taskFactory, CancellationToken cancellationToken = default(CancellationToken))
{
await _semaphore.WaitAsync(cancellationToken);
try
{
return await taskFactory().ConfigureAwait(false);
}
finally
{
_semaphore.Release(1);
}
}
}
and a simple console app to test
class Program
{
static void Main(string[] args)
{
var worker = new TaskWorker(1);
var cts = new CancellationTokenSource();
var token = cts.Token;
var tasks = Enumerable.Range(1, 10)
.Select(e => worker.RunAsync(() => SomeWorkAsync(e, token), token))
.ToArray();
Task.WhenAll(tasks).GetAwaiter().GetResult();
}
static async Task SomeWorkAsync(int id, CancellationToken cancellationToken)
{
Console.WriteLine($"Some Started {id}");
await Task.Delay(2000, cancellationToken).ConfigureAwait(false);
Console.WriteLine($"Some Finished {id}");
}
}
Update
TaskWorker implementing IDisposable
class TaskWorker : IDisposable
{
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
private readonly SemaphoreSlim _semaphore;
private readonly int _maxDegreeOfParallelism;
public TaskWorker(int maxDegreeOfParallelism)
{
if (maxDegreeOfParallelism <= 0)
{
throw new ArgumentOutOfRangeException(nameof(maxDegreeOfParallelism));
}
_maxDegreeOfParallelism = maxDegreeOfParallelism;
_semaphore = new SemaphoreSlim(maxDegreeOfParallelism, maxDegreeOfParallelism);
}
public async Task RunAsync(Func<Task> taskFactory, CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cts.Token))
{
// No ConfigureAwait(false) here to keep the SyncContext if any
// for the real task
await _semaphore.WaitAsync(cts.Token);
try
{
await taskFactory().ConfigureAwait(false);
}
finally
{
_semaphore.Release(1);
}
}
}
public async Task<T> RunAsync<T>(Func<Task<T>> taskFactory, CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cts.Token))
{
await _semaphore.WaitAsync(cts.Token);
try
{
return await taskFactory().ConfigureAwait(false);
}
finally
{
_semaphore.Release(1);
}
}
}
private void ThrowIfDisposed()
{
if (disposedValue)
{
throw new ObjectDisposedException(this.GetType().FullName);
}
}
#region IDisposable Support
private bool disposedValue = false;
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
_cts.Cancel();
// consume all semaphore slots
for (int i = 0; i < _maxDegreeOfParallelism; i++)
{
_semaphore.WaitAsync().GetAwaiter().GetResult();
}
_semaphore.Dispose();
_cts.Dispose();
}
disposedValue = true;
}
}
public void Dispose()
{
Dispose(true);
}
#endregion
}
You can think that thread is returned to a ThreadPool even thought it is not actauly a return. The thread simply picks next queued item when async operation starts.
I would suggest you to look at Task.Run instead of Task.Factory.StartNew Task.Run vs Task.Factory.StartNew.
And also have a look at TPL DataFlow. I think it will match your requirements.
I have this asynchronous method :
private static async Task Initializ( ) { /*Do Stuff Here*/ }
I want to be able to monitor the task that results from calling this function :
Task T = Class.Initialize( );
if (T.IsCancelled){ /*Do Stuff Here*/ }
I have in place a CancellationTokenSource.
How can I make T (or the function Initialize) utilize that sources token such that if it is cancelled, T.IsCancelled will be true?
EDIT
I do not know for certain but I think the answer to my question lies within using a TaskCompletionSource object. The answer given by Mike has lead me to this conclusion...
From the documentation
A Task will complete in the TaskStatus.Canceled state under any of
the following conditions:
Its CancellationToken was marked for cancellation before the task
started executing,
The task acknowledged the cancellation request on its already signaled
CancellationToken by throwing an OperationCanceledException that bears
the same CancellationToken.
The task acknowledged the cancellation request on its already signaled
CancellationToken by calling the ThrowIfCancellationRequested method
on the CancellationToken.
Updated:
Use this method:
async Task<Task> UntilCompletionOrCancellation(Task asyncOp, CancellationToken ct)
{
var tcs = new TaskCompletionSource<bool>();
using(ct.Register(() => tcs.TrySetResult(true)))
await Task.WhenAny(asyncOp, tcs.Task);
return asyncOp;
}
Consuming task:
var cts = new CancellationTokenSource();
await UntilCompletionOrCancellation(Class.Initialize, cts.Token);
if (!Class.Initialize.IsCompleted)
{
/*Do Stuff Here*/
}
Another approach is to remove async from Initialize
private static Task Initialize()
{
var tcs = new TaskCompletionSource();
//use TrySetResult or TrySetCancelled
return tcs.Task;
}
You can await this task and check whether is canceled or completed.
It is enough to just throw an OperationCanceledException from your asynchronous method.
The following writes true to the console:
public static void Main()
{
Console.WriteLine(DoSomethingAsync().IsCanceled);
}
private static async Task DoSomethingAsync()
{
throw new OperationCanceledException();
}
A nicer way to support cancellation is to have your asynchronous method take a CancellationToken as a parameter, it may then use this token to check for cancellation, e.g:
public static async Task DoSomethingAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
}
You need to call CancellationToken.ThrowIfCancellationRequested() inside the method and let the exception bubble up.
CancellationTokenSource cts = new CancellationTokenSource();
Task T = Class.Initialize(cts.Token);
if (T.IsCancelled){ /*Do Stuff Here*/ }
private static async Task Initializ(CancellationToken token )
{
/*Do Stuff Here*/
token.ThrowIfCancellationRequested();
/*Do More Stuff Here*/
}