I'm trying to cancel a cosmos query using the cancellation token using the NuGet package Microsoft.Azure.Cosmos (3.13.0).
Can anyone help explain why it takes so long to cancel?
This test shows that it takes 2000+ Milliseconds to cancel. I was expecting it to fail milliseconds after I cancelled it.
00158: Reading next
00160: Read next
00188: Cancelling
02492: The operation was canceled.
public class CosmosCancelationTests
{
private readonly ITestOutputHelper testOutputHelper;
public CosmosCancelationTests(ITestOutputHelper testOutputHelper)
{
this.testOutputHelper = testOutputHelper;
}
[Fact]
public async Task TestCancelationTime()
{
Stopwatch stopwatch = Stopwatch.StartNew();
try
{
var client = new CosmosClient(
"https://localhost:8081/",
"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="
);
var database = client.GetDatabase("CosmosDBPackage"); // Make sure this database is created in the cosmos instance
var container = database.GetContainer("SampleEntity"); // Make sure this container is created in the database
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
using var feedIterator = container.GetItemLinqQueryable<SampleEntity>()
.Where(x => false)
.ToFeedIterator();
if (feedIterator.HasMoreResults)
{
testOutputHelper.WriteLine($"{stopwatch.ElapsedMilliseconds:D5}: Reading next");
var task = feedIterator.ReadNextAsync(cancellationToken);
testOutputHelper.WriteLine($"{stopwatch.ElapsedMilliseconds:D5}: Read next");
await Task.Delay(20);
cancellationTokenSource.Cancel();
testOutputHelper.WriteLine($"{stopwatch.ElapsedMilliseconds:D5}: Cancelling");
await task;
}
}
catch (CosmosOperationCanceledException e)
{
testOutputHelper.WriteLine($"{stopwatch.ElapsedMilliseconds:D5}: {e.Message}");
}
}
}
CancellationTokens in .NET is a mechanism that by definition, won't cause any library to stop what it's doing immediately, it is a cooperative cancellation. The library will cancel the operation when it's safe and won't cause a corrupt or invalid state. For example, HttpClient, when you call SendAsync and the token cancels, if the client already started buffering the response, it won't cancel, it will wait until it completes.
In the case of the query, it will check the token, when it is safe and won't cause an invalid state, maybe the cancellation is happening while requests are on the wire, or responses are being aggregated.
Reference: https://learn.microsoft.com/en-us/dotnet/standard/threading/cancellation-in-managed-threads
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.
So I'm spinning up a HttpListener to wait for an OAuth2 response. In an ideal world, this is only going to be alive for a few seconds while the user logs in in the browser and we get posted the token.
I'd also like for this to have a CancellationToken so that the user can stop listening after a delay should they so wish.
My initial idea was to use something along the lines of:
_listener.Start();
Task<HttpListenerContext> t = _listener.GetContextAsync();
while (!cancelled.IsCancellationRequested)
{
if (t.IsCompleted)
{
break;
}
await Task.Run(() => Thread.Sleep(100));
}
HttpListenerContext ctx = t.Result;
//...
_listener.Stop();
But that doesn't sit right with me for so many reasons (weird async usage, polling, etc.).
So then I thought I might be able to use the synchronous version _listener.GetContext() in conjunction with Task.Run(func<T>, CancellationToken):
_listener.Start()
HttpListenerContext ctx = await Task.Run(() => _listener.GetContext(), cancelled);
//...
_listener.Stop();
This is a little better, the code's at least tidier, although it seems hacky using a synchronous version of the method asynchronously with a Task...
However this doesn't behave how I'd expect (aborting the running task when the token is cancelled).
This strikes me as something that really ought to be fairly simple to do so I assume I'm missing something.
So my question is thus... How do I listen asynchronously with a HttpListener in a cancellable fashion?
Because the GetContextAsync method does not support cancellation, it basically means that it is unlikely you can cancel the actual IO operation, yet unlikely to cancel the Task returned by the method, until you Abort or Stop the HttpListener. So the main focus here is always a hack that returns the control flow to your code.
While both the answers from #guru-stron and #peter-csala should do the trick, I just wanted to share another way without having to use Task.WhenAny.
You could wrap the task with a TaskCompletionSource like this:
public static class TaskExtensions
{
public static Task<T> AsCancellable<T>(this Task<T> task, CancellationToken token)
{
if (!token.CanBeCanceled)
{
return task;
}
var tcs = new TaskCompletionSource<T>();
// This cancels the returned task:
// 1. If the token has been canceled, it cancels the TCS straightaway
// 2. Otherwise, it attempts to cancel the TCS whenever
// the token indicates cancelled
token.Register(() => tcs.TrySetCanceled(token),
useSynchronizationContext: false);
task.ContinueWith(t =>
{
// Complete the TCS per task status
// If the TCS has been cancelled, this continuation does nothing
if (task.IsCanceled)
{
tcs.TrySetCanceled();
}
else if (task.IsFaulted)
{
tcs.TrySetException(t.Exception);
}
else
{
tcs.TrySetResult(t.Result);
}
},
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
return tcs.Task;
}
}
And flow the control like this:
var cts = new CancellationTokenSource();
cts.CancelAfter(3000);
try
{
var context = await listener.GetContextAsync().AsCancellable(cts.Token);
}
catch (TaskCanceledException)
{
// ...
}
I would suggest creating cancelable infinite task (Task.Delay(Timeout.Infinite, token) for example) and use Task.WhenAny. Something like that:
var cts = new CancellationTokenSource(); // token source controled by consumer "outside"
var token = cts.Token;
var httpListener = new HttpListener();
httpListener.Start();
var t = httpListener.GetContextAsync();
// to cancel the infinite delay task if listener finishes first
var localCts = CancellationTokenSource.CreateLinkedTokenSource(token);
var completed = await Task.WhenAny(t, Task.Delay(Timeout.Infinite, localCts.Token));
if (completed == t) // check that completed task is one from listener
{
localCts.Cancel(); // cancel the infinite task
HttpListenerContext ctx = t.Result;
//...
}
httpListener.Stop();
Here is yet another solution:
var cancellationSignal = new TaskCompletionSource<object>();
var contextTask = _listener.GetContextAsync();
using (cancelled.Register(state => ((TaskCompletionSource<object>)state).TrySetResult(null), cancellationSignal))
{
if (contextTask != await Task.WhenAny(contextTask, cancellationSignal.Task).ConfigureAwait(false))
break; //task is cancelled
}
Because we can't await the CancellationToken that's why have to apply the following trick
The CancellationToken does expose a Register method, where we can define a callback which will be called whenever the cancellation occurs
Here we can provide a delegate which sets an awaitable to completed
So, we can await that task
In order to create a Task which is set to completed whenever the cancellation occurs I've used TaskCompletionSource. You could also use SemaphoreSlim or any other signalling object which has async wait, like AsyncManualResetEvent.
So, we pass the cancellationSignal to the Register as a state parameter
Inside the delegate we have to cast it back to TCS to be able to call the TrySetResult on it
Inside the using block we await a Task.WhenAny
It will return that Task which finishes first
If that Task is the cancellation then we can break / return / throw ...
If that Task is the contextTask then we can continue the normal flow
I have the following two applications
Angular 6/7 App
.Net Core Web API
I am making GET request to API using Angular's HttpClient as shown below
this.subscription = this.httpClient.get('api/Controller/LongRunningProcess')
.subscribe((response) =>
{
// Handling response
});
API controller's LongRunningProcess method has the following code
[HttpGet]
[Route("LongRunningProcess")]
public async Task<IActionResult> LongRunningProcess(CancellationToken cancellationToken)
{
try
{
// Dummy long operation
await Task.Factory.StartNew(() =>
{
for (int i = 0; i < 10; i++)
{
// Option 1 (Not working)
if (cancellationToken.IsCancellationRequested)
break;
// Option 2 (Not working)
cancellationToken.ThrowIfCancellationRequested();
Thread.Sleep(6000);
}
}, cancellationToken);
}
catch (OperationCanceledException e)
{
Console.WriteLine($"{nameof(OperationCanceledException)} thrown with message: {e.Message}");
}
return Ok();
}
Now I want to cancel this long-running process so I am unsubscribing from client side as shown below
// On cancel button's click
this.subscription.unsubscribe();
Above code will cancel the request and I can see it is canceled in the Network tab of the browser as shown below
But it is not going to make IsCancellationRequested to true in the method LongRunningProcess of the API, so the operation will keep going.
[Note]: Both Option 1 and Option 2 in API method are not working even if I make a call using postman.
Question: Is there any way to cancel that LongRunningProcess method's operation?
When angular cancel request, you can get cancellation token from http context
CancellationToken cancellationToken = HttpContext.RequestAborted;
if (cancellationToken.IsCancellationRequested)
{
// The client has aborted the request
}
You dont need break in this case only use like this
[HttpGet]
[Route("LongRunningProcess")]
public async Task<IActionResult> LongRunningProcess(CancellationToken cancellationToken)
{
for (int i = 0; i < 10; i++)
{
cancellationToken.ThrowIfCancellationRequested();
// Dummy long operation
await Task.Factory.StartNew(() => Thread.Sleep(60000));
}
return Ok();
}
You can read it more here
This is because your dummy long operation does not monitor the canncellationToken. I'm not sure it is actually your intention to start 10 one-minute tasks all in parallel without any delay, which is what your code does.
In order to have a dummy long operation, the code would be like
[HttpGet]
[Route("LongRunningProcess")]
public async Task<IActionResult> LongRunningProcess(CancellationToken cancellationToken)
{
// Dummy long operation
await Task.Run(() =>
{
for (var i = 0; i < 60; i++)
{
if (cancel.IsCancellationRequested)
break;
Task.Delay(1000).Wait();
}
});
return Ok();
}
Task.Run is just equivalent to Task.Factory.StartNew, by the way.
However, if you just need a dummy long-run operation in your web API, then you can also simply use Task.Delay, which supports cancellation token. Task.Delay throws an exception when the request is canceled, so add exception handling code when you need to do something after request cancellation.
[HttpGet]
[Route("LongRunningProcess")]
public async Task<IActionResult> LongRunningProcess(CancellationToken cancellationToken)
{
// Dummy long operation
await Task.Delay(60000, cancel);
return Ok();
}
Any http observables still running at the time will complete and run their logic unless you unsubscribe in onDestroy(). Whether the consequences are trivial or not will depend upon what you do in the subscribe handler. If you try to update something that doesn't exist anymore you may get an error.
Tip: The Subscription contains a closed boolean property that may be useful in advanced cases. For HTTP this will be set when it completes. In Angular it might be useful in some situations to set a _isDestroyed property in ngDestroy which can be checked by your subscribe handler.
Tip 2: If handling multiple subscriptions you can create an ad-hoc new Subscription() object and add(...) any other subscriptions to it - so when you unsubscribe from the main one it will unsubscribe all the added subscriptions too.
So, best practice is to use takeUntil() and unsubscribe from http calls when the component is destroyed.
import { takeUntil } from 'rxjs/operators';
.....
ngOnDestroy(): void {
this.destroy$.next(); // trigger the unsubscribe
this.destroy$.complete(); // finalize & clean up the subject stream
}
var cancellationToken = new CanellationToken();
cancellationToken.CancelAfter(2000);
using (var response = await _httpClient.GetAsync("emp",
HttpCompletionOption.ResponseHeadersRead, cancellationTokenSource.Token))
{
response.EnsureSuccessStatusCode();
var stream = await response.Content.ReadAsStreamAsync();
var emp = await JsonSerializer.DeserializeAsync<List<empDto>>(stream, _options);
}
Further we can also have this "CancellationToken" class, which is nothing much Http client method which terminates the request after certain time-interval.
In angular subscription.unsubscribe(); closes the channel and causes CORE to cancel the API caller's thread, that's good.
Don't use await Task.Run(()... This creates a result/task that should be disposed, if not, the task keeps going, your pattern doesn't permit this - that's why it continues to run.
Simply - 'await this.YourLongRunningFunction()', I'm pretty sure that when the owning thread throws the OperationCancelled exception your task will end.
If "3" doesn't work, then pass a cancellation token to your long running task and set that when you catch your OperationCancelled exception.
I am having below lines of code:
private static IReadOnlyList<GattDeviceService> GetGattServicesAsync(BluetoothLEDevice device, bool useUnCachedServices)
{
GattDeviceServicesResult services = useUnCachedServices ? device.GetGattServicesAsync(BluetoothCacheMode.Uncached).GetResults() : device.GetGattServicesAsync().GetResults();
Global.Log.TraceOut();
// Return list anyway
return services.Services;
}
This is called from below line:
var services = ApiInformation.IsMethodPresent("Windows.Devices.Bluetooth.BluetoothLEDevice", "GetGattServicesAsync", 1) ?
GetGattServicesAsync(device, useUnCachedServices) :
device.GattServices;
I am using GetGattServicesAsync() call used to retrieve services from mobile device.In one of scenario,I was unable to gets services from Mobile device and UI stuck there.I want to suspend this call if the response won't come in 10 seconds. I thought to achieve this using Task and cancellation token.But found that this call is working only when task is run as synchronously and thus i was not able to cancel the task using cancellation token.Please help to achieve this,May be in simplest way:
Referred links:
https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/cancel-async-tasks-after-a-period-of-time
How to cancel a task that is waiting with a timeout without exceptions being thrown
I hope this code helps you.
you can use Task
private static IReadOnlyList<GattDeviceService>GetGattServicesAsync(BluetoothLEDevice device, bool useUnCachedServices)
{
var CancellationTokenSource = new CancellationTokenSource();
var CancellationToken = new CancellationTokenSource.Token;
var GetResults = task.Run(async ()=> awite useUnCachedServices ? device.GetGattServicesAsync(BluetoothCacheMode.Uncached).GetResults() : device.GetGattServicesAsync().GetResults(),_cancellationToken);
WaitHandle.WaitAny(new[] { cancellationTokenSource.Token.WaitHandle }, TimeSpan.Fromseconds(10));
Global.Log.TraceOut();
// Return list anyway
return services.Services;
}
With work As soon as _cancellationToken = true Tack No longer continues.
Given code similar to
Task.Run(() =>
{
using (var client = new HttpClient())
{
var responseTask = client.GetAsync(urlToInvoke);
}
});
In a situation like this, it appears that GetAsync does not actually operate. Is the task canceled prior to completion or what is actually going on here?
Now if you change things slightly and insert
Task.Run(() =>
{
using (var client = new HttpClient())
{
var responseTask = client.GetAsync(urlToInvoke);
Task.Delay(5000).Wait()
}
});
GetAsync does execute completely. What is going on here? Is Task.Delay affinitizing itself to the same Task that is inside responseTask ultimately making this equivalent to responseTask.Wait()?
You are thinking of it incorrectly. Here is pseudo version of what is happening inside the class.
class HttpClient : IDisposeable
{
private CancelationTokenSource _disposeCts;
public HttpClient()
{
_disposeCts = new CancelationTokenSource();
}
public Task<HttpResponseMessage> GetAsync(string url)
{
return GetAsync(url, CancellationToken.None);
}
public async Task<HttpResponseMessage> GetAsync(string url, CancelationToken token)
{
var combinedCts =
CancellationTokenSource.CreateLinkedTokenSource(token, _disposeCts.Token);
var tokenToUse = combinedCts.Token;
//... snipped code
//Some spot where it would good to check if we have canceled yet.
tokenToUse.ThrowIfCancellationRequested();
//... More snipped code;
return result;
}
public void Dispose()
{
_disposeCts.Cancel();
}
//... A whole bunch of other stuff.
}
The important thing to see is when you exit the using block a internal cancelation token is canceled.
In your first example the task had not finished yet so tokenToUse would now throw if ThrowIfCancellationRequested() was called.
In your second example the task had already finished so the act of canceling the internal token had no effect on the task that was returned due to it already reaching the completed state.
It is like asking why this causes the task to be canceled.
using (var client = new HttpClient())
{
var cts = new CancellationTokenSource()
var responseTask = client.GetAsync(urlToInvoke, cts.Token);
cts.Cancel();
}
but this does not
using (var client = new HttpClient())
{
var cts = new CancellationTokenSource()
var responseTask = client.GetAsync(urlToInvoke, cts.Token);
Task.Delay(5000).Wait()
cts.Cancel();
}
When you don't await (or Wait) tasks they do not cancel themselves. They continue to run until they reach one of three statuses:
RanToCompletion - Finished successfully.
Canceled - The cancellation token was canceled.
Faulted - An unhandled exception occurred inside the task.
In your case however, because no one waits for the task to complete, the using scope ends which disposes of the HttpClient. This in turn will cancel all the client's tasks, client.GetAsync(urlToInvoke) in this case. So that inner async task will end immediately and become Canceled, while the outer task (Task.Run) will simply end without doing anything.
When you use Task.Delay(5000).Wait() which is basically Thread.Sleep(5000) the task has a chance to complete before the using scope ends. That mode of operation however should be avoided. It blocks a thread throughout the Waitand could lead to deadlocks in single threaded SynchronizationContexts. This also hides possible exceptions in the task (which could tear down the application in earlier versions of .Net)
You should always wait for tasks to complete, preferably asynchronously, and as Servy commented, there's no reason to use Task.Run here for offloading because GetAsyncis asynchronous and won't block the calling thread.
using (var client = new HttpClient())
{
var response = await client.GetAsync(urlToInvoke);
}