FindAsync CancellationToken fail - c#

The code below has a for loop to test that the cancellation token is working when attempting to retrieve a record from the Activity table:
public class Handler : IRequestHandler<Query, Result<Activity>>
{
private readonly ReactivitiesContext _context;
private readonly ILogger<Activity> _logger;
public Handler(ReactivitiesContext context, ILogger<Activity> logger)
{
_context = context;
_logger = logger;
}
public async Task<Result<Activity>> Handle(Query request,
CancellationToken cancellationToken)
{
try
{
for (int i = 1; i <= 5; i++)
{
cancellationToken.ThrowIfCancellationRequested();
await Task.Delay(1000, cancellationToken);
_logger.LogInformation($"{i} second(s) have elapsed");
}
}
catch (Exception)
{
_logger.LogInformation("Cancellation was done");
}
return Result<Activity>.Success(await _context.Activities.FindAsync(
new object[] { request.Id }, cancellationToken));
}
}
However when cancelling the request (through Postman), it does not cancel. Instead, the request returns the correct "Activity" object, and the for loop runs its course, even though I cancel after 2-3 seconds have elapsed. See logs:
info: Domain.Activity[0]
1 second(s) have elapsed
info: Domain.Activity[0]
2 second(s) have elapsed
info: Domain.Activity[0]
3 second(s) have elapsed
info: Domain.Activity[0]
4 second(s) have elapsed
info: Domain.Activity[0]
5 second(s) have elapsed
info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
Entity Framework Core 6.0.6 initialized 'ReactivitiesContext' using provider 'Microsoft.EntityFrameworkCore.SqlServer:6.0.6' with options: None
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (35ms) [Parameters=[#__get_Item_0='?' (DbType = Guid)], CommandType='Text', CommandTimeout='30']
SELECT TOP(1) [a].[Id], [a].[Category], [a].[City], [a].[Date], [a].[Description], [a].[Title], [a].[Venue]
FROM [Activities] AS [a]
WHERE [a].[Id] = #__get_Item_0
The FindAsync cancellation approach used closely follows the guidance provided in this GitHub issue: https://github.com/dotnet/efcore/issues/12012
Here they advise to pass the FindAsync params as an array, followed by the cancellation token.
Can anyone see what needs to be corrected in order for the cancellation token to work?
Thank you in advance.
Edit 1/11/23:
Thank you to Nikolay, who solved my issue with his last comment from 1/10. My controller was not passing the cancellation token - here is the corrected controller, which has corrected the issue and 'cancel' is now working:
[HttpGet("{id}")]
public async Task<IActionResult> GetActivity(Guid id, CancellationToken token)
{
return ResultHandler(await Mediator.Send(new Details.Query { Id = id }, token));
}
Nikolay - if you post this as an answer, I will mark this as the answer. In the meantime, I upvoted all of your comments. Thank you again for your much appreciated insight!

Related

Asynchronus insert in Azure cloud table

I am trying to insert a record in azure table on some specific condition. What I found that the records are not getting saved all the time. Few times it works and few times its not. Here is the code which I have written to execute the insert. In addition to that, the code is written to insert a single record for each call and its never going to save multiple records at a time. Also I don't want to wait for the insert operation nor I want to do it synchronously. I want to do it asynchronously and that is without wait.
why the code sometimes fails to insert the records?
Here is my code snippet
try
{
AuditUtils utils = new AuditUtils((int)this.User.Id);
String tableName = "ServiceHeaderInfo", partitionKey = this.Header.PrimaryAccount;
TableAuditEntity tableAuditEntity = utils.GetAuditEntity(tableName, partitionKey, Guid.NewGuid().ToString(), TableAuditTypes.Insert, refId, refType);
tableAuditEntity["DeviceIdentifier"] = this.Header.DeviceId;
tableAuditEntity["DeviceModel"] = this.Header.DeviceModel;
tableAuditEntity["OSVersion"] = this.Header.OSVersion;
utils.AddAsync(tableAuditEntity).ContinueWith(t =>
{
var ex = t.Exception?.GetBaseException();
if (ex != null)
{
this.HandleError(ex);
}
}, System.Threading.Tasks.TaskContinuationOptions.OnlyOnFaulted);
}
catch (Exception ex)
{
this.HandleError(ex);
}
public async Task<TableResult> AddAsync(string tableName, SampleTableEntity entity, CancellationToken cancellationToken = default(CancellationToken))
{
var table = await GetTableAsync(tableName);
return await table.ExecuteAsync(TableOperation.InsertOrReplace(entity), cancellationToken);
}
​It is not possible Async without Wait.
You need to use
ExecuteAsync(TableOperation)`
or
ExecuteAsync(TableOperation, TableRequestOptions, OperationContext) methods is used to achieve the requirement.
Instead of above methods you have
ExecuteAsync(TableOperation, CancellationToken)
&
ExecuteAsync(TableOperation, TableRequestOptions, OperationContext, CancellationToken)
When you use CancellationToken it must have a TaskFactory.ContinueWhenAll method.
In a cancellation of the operation, it instantiates a CancellationTokenSource object that generates a cancellation token that's passed to a TaskFactory object.
In turn, the TaskFactory object passes the cancellation token to each of the tasks responsible for collecting readings for a particular instrument.
The TaskFactory.ContinueWhenAll<TAntecedentResult, TResult>(Task<TAntecedentResult>[], Func<Task<TAntecedentResult>[],TResult>, CancellationToken) method is called to ensure that computed only after all readings have been gathered successfully.
If a task has not completed because it was cancelled, the TaskFactory.ContinueWhenAll method throws an exception.
Please refer: click here

Why does it take so long to cancel a Cosmos Query?

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

Call async method with await in it from synchronous action method

I need to implement a service which fire a start of the processing. But I don't need to wait for result. I can just show the default output and in the background the process will be working.
However I came up to the problem that the code after await is not executed.
I prepared some code to show the idea:
public class HomeController : Controller
{
public ActionResult Deadlock()
{
AsyncCall();
return View();
}
private async Task AsyncCall()
{
await Task.Delay(10000);
var nonreachablePlace = "The breakpoint will not set the execution here";
Do(nonreachablePlace);
}
private void Do(string m)
{
m.Contains("x");
}
}
I know that it looks very bad. But the idea was like:
A thread go to Deadlock method.
The thread go to AsyncCall method synchronously.
Faces the await statement.
Go from the Deadlock method.
Continue main method to the end.
When Task.Delay finished, this thread will come up from the thread pool and continue working.
To my bad 6 step is not processed. I have tried to set up the breakpoint and never got hit.
But if I reduce the time delay and do it in debug I will come to the 6 step.
Enter to the controller's action method
After return from controller's action method
But if I leave only one breakpoint after await, I won't go to the 6 step
var nonreachablePlace = "The breakpoint will not set the execution here";
NOTE:
I append ConfigureAwait(false) to the Task.Delay(). It looks like this:
private async Task AsyncCall()
{
await Task.Delay(10000).ConfigureAwait(false);
var nonreachablePlace = "The breakpoint will not set the execution here";
Do(nonreachablePlace);
}
And now it works as expected.
My question is why does the code not work without ConfigureAwait(false)?
Maybe it somehow related to SynchronizationContext and it is not reachable after the main thread finishes its work. And after that awaitable method tryes to get a context when it has been already disposed (Just my thought)
Use HostingEnvironment.QueueBackgroundWorkItem.
Note this is only available in Classic ASP.NET on .NET Framework (System.Web.dll) and not ASP.NET Core (I forget to what extent it works in ASP.NET Core 1.x and 2.x running on .NET Framework, but anyway).
All you need is this:
using System.Web.Hosting;
public class MyController : Controller
{
[HttpPost( "/foo" )]
public async Task<ActionResult> DoSomething()
{
HostingEnvironment.QueueBackgroundWorkItem( this.DoSomethingExpensiveAsync ); // Pass the method by name or as a `Func<CancellationToken,Task>` delegate.
return this.View();
}
private async Task DoSomethingExpensiveAsync( CancellationToken cancellationToken )
{
await Task.Delay( TimeSpan.FromSeconds( 30 ) );
}
}
You can also use it with non-async workloads:
[HttpPost( "/foo" )]
public async Task<ActionResult> DoSomething()
{
HostingEnvironment.QueueBackgroundWorkItem( this.DoSomethingExpensive ); // Pass the method by name or as a `Action<CancellationToken>` delegate.
return this.View();
}
private void DoSomethingExpensive( CancellationToken cancellationToken )
{
Thread.Sleep( 30 * 1000 ); // NEVER EVER EVER call Thread.Sleep in ASP.NET!!! This is just an example!
}
If you want to start a job normally at first and only finish it in the background if it takes too long, then do this:
[HttpPost( "/foo" )]
public async Task<ActionResult> DoSomething()
{
Task<String> workTask = this.DoSomethingExpensiveAsync( default );
Task timeoutTask = Task.Delay( TimeSpan.FromSeconds( 5 ) );
Task first = await Task.WhenAny( workTask, timeoutTask );
if( first == timeoutTask )
{
// `workTask` is still running, so resume it in the background:
HostingEnvironment.QueueBackgroundWorkItem( async ct => await workTask );
return this.View( "Still working..." );
}
else
{
// `workTask` finished before the timeout:
String result = await workTask; // or just `workTask.Result`.
return this.View( result );
}
}
private async Task<String> DoSomethingExpensiveAsync( CancellationToken cancellationToken )
{
await Task.Delay( TimeSpan.FromSeconds( 30 ) );
return "Explosive bolts, ten thousand volts; At a million miles an hour, Abrasive wheels and molten metals";
}
So Deadlock() calls AsyncCall()
Then AsyncCall() Tells DeadLock() "Okay, well I'm waiting for Task.Delay to count to 10,000 but you can go ahead."
...so AsyncCall() yields the main thread back over to DeadLock().
Now DeadLock() never said anything about waiting on AsyncCall() to finish, so as far as DeadLock() is concerned, AsyncCall() has already returned (it actually only yielded, but the program cursor would still be passed back out into DeadLock().
So I would suggest setting your breakpoint at the AsyncCall() method in DeadLock(), because you'll probably see that your main thread is already done and exited before the Task.Delay() is even done.
So AsyncCall() never even gets a chance to finish Awaiting.
I dived deep into the logic behind Task.Delay(10000) and continuation code after that.
Thanks to the post made by Stephen Toub.
The main problem was in part when a Task had finished. And it's result needed to be processed by next thread.
Since I hadn't written ConfigureAwait() I implicitly meant to run the code in a thread which has SynchronizationContext (AspNetSynchronizationContext in my case).
private async Task AsyncCall()
{
/// The delay is done by a thread from a ThreadPool.
await Task.Delay(10000);
/// After the Task has been finished
/// TaskAwaiter tryies to send a continuation code to a thread with
/// SynchronizationContext.
var nonreachablePlace = "The breakpoint will not set the execution here";
Do(nonreachablePlace);
}
Because I hadn't wanted to wait the result of awaitable, I returned the response from controller's action. Then the thread went to ThreadPool and SynchronizationContext was disposed.
To the moment of Task completion, there was no SynchronizationContext to send a delegate with continuation code.
And during the code creation Visual Studio Enabled Just My Code option was set to true. That's why this exception was thrown silently to me.
And about the situation when I was able to run a code even I had Task.Delay(2000). I think it's caused by the time needed to Classic ASP.NET to complete a request and create a response to it. During this time you can get a reference to SynchronizationContext and Post a delegate to it.

How to cancel .Net Core Web API request using Angular?

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.

What is the use of Cancellation token here

I've the following chunk of code :
using (var cancelSource = new CancellationTokenSource())
{
Task[] tasks = null;
var cancelToken = cancelSource.Token;
tasks = new[]
{
Task.Run(async () => await ThrowAfterAsync("C", cancelToken, 1000)) //<---
};
await Task.Delay(howLongSecs * 1000); // <---
cancelSource.Cancel();
await Task.WhenAll(tasks);
}
Where ThrowAfterAsync have this :
private async Task ThrowAfterAsync(string taskId, CancellationToken cancelToken, int afterMs)
{
await Task.Delay(afterMs, cancelToken);
var msg = $"{taskId} throwing after {afterMs}ms";
Console.WriteLine(msg);
throw new ApplicationException(msg);
}
Resharper is suggesting me that I can use the overload of Task.Run() with cancellation token like this :
Task.Run(async () => await ThrowAfterAsync("C", cancelToken, 1000), cancelToken)
But why ? What is the benefit of doing this over the first version, without the cancellation token as parameter ?
In this specific case, there is no point. In general, you'd want to do as it suggests because, by passing the token to Task.Run it can avoid even scheduling the operation in the first place if the token is cancelled before the operation even has a chance to start, but in your case you're creating the token and you know it won't be cancelled when you start the operation.
But the reason you don't need to pass the token to Task.Run is because the code starting that task is the operation responsible for cancelling the token, and so it knows that the token isn't cancelled yet. Normally you'd be accepting a token from elsewhere, and you wouldn't know if/when it was cancelled.
All that said, there's no reason to even use Task.Run at all. You can just write:
tasks = new[] { ThrowAfterAsync("C", cancelToken, 1000) };
It will have the same behavior but without needlessly starting a new thread just to start the asynchronous operation.
Next, your code will never return in less than howLongSecs seconds, even if the operation finishes before then, because of how you've structured your code. You should simply provide the timeout to the cancellation token source and let it take care of canceling the token at the right time, it won't delay the rest of your method if the operation finishes before the cancellation should happen, so your whole method can just be written as:
using (var cancelSource = new CancellationTokenSource(Timespan.FromSeconds(howLongSecs)))
{
await ThrowAfterAsync("C", cancelToken, 1000)
}
Resharper sees that you are using a method (Task.Run) which has overload which accepts CancellationToken, you have instance of CancellationToken in scope, but you do not use that overload which accepts a token. It does not perform any extensive analysys of your code - it's as simple as that. You can easily verify this with this code:
class Program {
static void Main() {
CancellationToken ct;
Test("msg"); // will suggest to pass CancellationToken here
}
private static void Test(string msg) {
}
private static void Test(string msg, CancellationToken ct) {
}
}
Yes the code itself is strange and you don't need to wrap your async in Task.Run at all, but I won't touch that since you asked just why Resharper suggests that.

Categories

Resources