How to cancel previous Task if new request recieved? - c#

I will try to simplify my situation here to be more clean and concise. So, I am working on a WinRT application where user enters text in a TextBox and in its TextChanged event after 2 seconds have elapsed I need to make a remote request to fetch data based on user text.
Now user enters text and a web request has been initialized but immediately user writes another term. So, I need to cancel the first web request and fire the new one.
Consider the following as my code :
private CancellationTokenSource cts;
public HomePageViewModel()
{
cts = new CancellationTokenSource();
}
private async void SearchPeopleTextChangedHandler(SearchPeopleTextChangedEventArgs e)
{
//Cancel previous request before making new one
//GetMembers() is using simple HttpClient to PostAsync() and get response
var members = await _myService.GetMembers(someId, cts.Token);
//other stuff based on members
}
I know CancellationToken plays a role here but I just cannot figure out how.

You've already almost got it. The core idea is that a single CancellationTokenSource can only be canceled once, so a new one has to be created for each operation.
private CancellationTokenSource cts;
private async void SearchPeopleTextChangedHandler(SearchPeopleTextChangedEventArgs e)
{
// If there's a previous request, cancel it.
if (cts != null)
cts.Cancel();
// Create a CTS for this request.
cts = new CancellationTokenSource();
try
{
var members = await _myService.GetMembers(someId, cts.Token);
//other stuff based on members
}
catch (OperationCanceledException)
{
// This happens if this operation was cancelled.
}
}

I would implement the GetMembers method like this:
private async Task<List<Member>> GetMembers(int id, CancellationToken token)
{
try
{
token.ThrowIfCancellationRequested();
HttpResponseMessage response = null;
using (HttpClient client = new HttpClient())
{
response = await client.PostAsync(new Uri("http://apiendpoint"), content)
.AsTask(token);
}
token.ThrowIfCancellationRequested();
// Parse response and return result
}
catch (OperationCanceledException ocex)
{
return null;
}
}
The rest is just calling the cts.Cancel() method and creating a new instance of CancellationTokenSource before calling GetMembers each time in the handler. Of course, as #Russell Hickey mentioned, cts should be global. (and even static if there are multiple instances of this class and you always want to cancel the GetMembers method when this handler is invoked. Usually I also have a class which wraps the result and has an additional property IsSuccessful to distinguish a real null result from a failed operation.

Related

How to call async method in a thread but wait for it in c#

I have a thread which is responsible for calling a webapi from 4 websites exactly every 2 seconds. The Webapi call method should not be awaited because if a website is not available it will wait 5 second to get timeout and then the next website call will be delayed.
As HttpClient in .NET 4.7.2 has only async methods , it should be used with await, and if not , compiler gives warning and we may get unexpected behavior (as Microsoft says) .
So should I use Task.Run or call Threadpool.QueueUserWorkItem to make a webapi call in parallel.
Here is sudocode :
public class Test1
{
private AutoResetEvent waitEvent = new AutoResetEvent(false);
private volatile bool _terminated = false;
public void Start()
{
Thread T = new Thread(ProcThread);
T.Start();
}
private async void ProcThread()
{
while (!_terminated)
{
await CallWebApi(); <=========== this line
waitEvent.WaitOne(2000);
}
}
private async Task CallWebApi()
{
HttpClient client = new HttpClient();
.....
.....
}
}
So you have an async procedure that uses a HttpClient to fetch some information and process the fetched data:
async Task CallWebApiAsync() {...}
Improvement 1: it is good practice to suffix async methods with async. This is done to make it possible to let an async version exist next to a non-async version that does something similarly.
Inside this method you are using one of the HttpClient methods to fetch the information. As CallWebApiAsync is awaitable, I assume the async methods are used (GetAsync, GetStreamAsync, etc), and that the method only awaits when it needs the result of the async method.
The nice thing about this is, that as a user of CallWebApiAsync, as long as you don't await the call, you are free to do other things, even if the website isn't reacting. The problem is: after 2 seconds, you want to call the method again. But what to do if the method hasn't finished yet.
Improvement 2 Because you want to be able to start a new Task, while the previous one has not finished: remember the started tasks, and throw them away when finished.
HashSet<Task> activeTasks = new HashSet<Task>(); // efficient add, lookup, and removal
void TaskStarted(Task startedTask)
{
// remember the startedTask
activeTasks.Add(startedTask);
}
void TaskCompleted(Task completedTask)
{
// If desired: log or process the results
LogFinishedTask(completedTask);
// Remove the completedTask from the set of ActiveTasks:
activeTasks.Remove(completedTask);
}
It might be handy to remove all completed tasks at once:
void RemoveCompletedTasks()
{
var completedTasks = activeTasks.Where(task => task.IsCompleted).ToList();
foreach (var task in completedTasks)
{
TaskCompleted(completedTask);
}
}
Now we can adjust your ProcThread.
Improvement 3: in async-await always return Task instead of void and Task<TResult> instead of TResult. Only exception: eventhandlers return void.
async Task ProcThread()
{
// Repeatedly: start a task; remember it, and wait 2 seconds
TimeSpan waitTime = TimeSpan.FromSeconds(2);
while (!terminationRequested)
{
Task taskWebApi = CallWebApiAsync();
// You didn't await, so you are free to do other things
// Remember the task that you started.
this.TaskStarted(taskWebApi);
// wait a while before you start new task:
await Task.Delay(waitTime);
// before starting a new task, remove all completed tasks
this.RemoveCompletedTasks();
}
}
Improvement 4: Use TimeSpan.
TimeSpan.FromSeconds(2) is much easier to understand what it represents than a value 2000.
How to stop?
The problem is of course, after you request termination there might still be some tasks running. You'll have to wait for them to finish. But even then: some tasks might not finish at all within reasonable time.
Improvement 5: use CancellationToken to request cancellation.
To cancel tasks in a neat way, class CancellationToken is invented. Users who start a task create a CancellationTokenSource object, and ask this object for a CancellationToken. This token is passed to all async methods. As soon as the user wants to cancel all tasks that were started using this CancellationTokenSource, he requests the CancellationTokenSource to cancel.
All tasks that have a token from this source have promised to regularly check the token to see if cancellation is requested. If so, the task does some cleanup (if needed) and returns.
Everything summarized in one class:
class Test1
{
private HttpClient httpClient = new HttpClient(...);
private HashSet<TTask> activeTasks = new HashSet<TTask>();
public async Task StartAsync(CancellationToken cancellationToken)
{
// repeated CallWebApiAsync until cancellation is requested
TimeSpan waitTime = TimeSpan.FromSeconds(2);
// repeat the following until OperationCancelled
try
{
while (true))
{
// stop if cancellation requested
cancellationToken.ThrowIfCancellationRequested();
var taskWebApi = this.CallWebApiAsync(cancellationToken);
this.activeTasks.Add(taskWebApi);
await Task.Delay(waitTime, cancellationToken);
// remove all completed tasks:
activeTasks.RemoveWhere(task => task.IsCompleted);
}
}
catch (OperationCanceledException exception)
{
// caller requested to cancel. Wait until all tasks are finished.
await Task.WhenAll(this.activeTasks);
// if desired do some logging for all tasks that were not completed.
}
}
And the adjusted CallWebApiAsync:
private async Task CallWebApiAsync(CancellationToken cancellationToken)
{
const string requestUri = ...
var httpResponseMessage = await this.httpClient.GetAsync(requestUri, cancellationToken);
// if here: cancellation not requested
this.ProcessHttpResponse(httpResponseMessage);
}
private void ProcessHttpRespons(HttpResponseMessage httpResponseMessage)
{
...
}
}
Usage:
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
Test1 test = new Test1();
Task taskCallWebApiRepeatedly = test.StartAsync(cancellationTokenSource.Token);
// because you didn't await, you are free to do other things, while WebApi is called
// every 2 seconds
DoSomethingElse();
// you get bored. Request cancellation:
cancellationTokenSource.Cancel();
// of course you need to await until all tasks are finished:
await Task.Wait(taskCallWebApiRepeatedly);
Because everyone promises to check regularly if cancellation is requested, you are certain that within reasonable time all tasks are finished, and have cleaned up their mess. The definition or "reasonable time" is arbitrary, but let's say, less than 100 msec?
If all you want is to execute a method every two seconds, then a System.Timers.Timer is probably the most suitable tool to use:
public class Test1
{
private readonly HttpClient _client;
private readonly System.Timers.Timer _timer;
public Test1()
{
_client = new HttpClient();
_timer = new System.Timers.Timer();
_timer.Interval = 2000;
_timer.Elapsed += Timer_Elapsed;
}
private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
var fireAndForgetTask = CallWebApiAsync();
}
private async Task CallWebApiAsync()
{
var html = await _client.GetStringAsync("http://example.com");
//...
}
public void Start() => _timer.Start();
public void Stop() => _timer.Stop();
}
something like this. BTW take this as pseudo code as I am typing sitting on my bed:)
List<Task> tasks = new List<Task>();
tasks.Add(CallWebApi());
while (! await Task.WhenAny(tasks))
{
tasks.Add(CallWebApi()); <=========== this line
await Task.Delay(2000);
}

Ensuring that an async task has fully cancelled before restarting that task

OK, here goes: I have a part of an application where I am querying rows from a database. I perform the query when the user enters text into a search box (or alters another filter setting).
The data that is returned from the database is going into an ObservableCollection which is bound to a DataGrid. Because I'm conscious of keeping the UI responsive, I'm using Async-Await to (attempt) to fill this ObservableCollection in the background.
So, in my mind, every time the user types something (or changes the filter settings) I want to cancel the ongoing task wait for it to confirm it's cancelled and then "restart" (or rather create a new task) with the new settings.
But I'm getting all sorts of weird results (especially when I slow down the task to simulate slow database access) such as the collection not getting cleared and being populated twice and when disposing the CancellationTokenSource (which I read is a good idea) sometimes when I get to the point of calling Cancel() it's been disposed in the meantime and I get an exception.
I suspect that the issue stems from a fundamental gap in my understanding of the pattern I'm meant to use here so any style/pattern pointers are as welcome as an actual technical solution.
The code basically goes like this:
ObservableCollection<Thing> _thingCollection;
Task _thingUpdaterTask;
CancellationTokenSource _thingUpdaterCancellationSource;
// initialisation etc. here
async void PopulateThings(ThingFilterSettings settings)
{
// try to cancel any ongoing task
if(_thingUpdaterTask?.IsCompleted ?? false){
_thingUpdaterCancellationSource.Cancel();
await _thingUpdaterTask;
}
// I'm hoping that any ongoing task is now done with,
// but in reality that isn't happening. I'm guessing
// that's because Tasks are getting dereferenced and
// orphaned in concurrent calls to this method?
_thingCollection.Clear();
_thingUpdaterCancellationSource = new CancellationTokenSource();
var cancellationToken = _thingUpdaterCancellationSource.Token;
var progressHandler = new Progress<Thing>(x => _thingCollection.add(x));
var progress = (IProgress<Thing>)progressHandler;
try{
_thingUpdaterTask = Task.Factory.StartNew(
() => GetThings(settings, progress, cancellationToken));
await _thingUpdaterTask;
}catch(AggregateException e){
//handle stuff etc.
}finally{
// should I be disposing the Token Source here?
}
}
void GetThings(ThingFilterSettings settings,
IProgress<Thing> progress,
CancellationToken ctok){
foreach(var thingy in SomeGetThingsMethod(settings)){
if(ctok.IsCancellationRequested){
break;
}
progress.Report(thingy);
}
}
You could add a wrapper class, that will wait for the previous task execution to stop (either by finishing or by canceling) before starting the new task.
public class ChainableTask
{
private readonly Task _task;
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
public ChainableTask(Func<CancellationToken, Task> asyncAction,
ChainableTask previous = null)
{
_task = Execute(asyncAction, previous);
}
private async Task CancelAsync()
{
try
{
_cts.Cancel();
await _task;
}
catch (OperationCanceledException)
{ }
}
private async Task Execute(Func<CancellationToken, Task> asyncAction, ChainableTask previous)
{
if (previous != null)
await previous.CancelAsync();
if (_cts.IsCancellationRequested)
return;
await asyncAction(_cts.Token);
}
}
If used the class above in previous projects. The class takes a lambda, the asyncAction to create the next task. The task is only created after the previous has finished.
It will pass a CancellationToken to each task, to allow the task to stop before finishing. Before the starting the next task, the token of the previous is canceled and the previous task is awaited. This happens in CancelAsync.
Only after the previous Cancel was awaited, we call the lambda to create the next task.
A usage example:
var firstAction = new ChainableTask(async tcs => await Task.Delay(1000));
var secondAction = new ChainableTask(async tcs => await Task.Delay(1000), firstAction ); // pass the previous action
In this example, the created task does not support cancellation, so the second call to ChainableTask will wait until the first Task.Delay(1000) finishes, before calling the second.

How to cancel many Async Tasks immediately when I use HttpClient?

I have a class like below.
class Service
{
private HttpClient client;
public Service()
{
client = new HttpClient();
client.BaseAddress = new Uri("a uriString"); //"a uriString" refers to a real uri string.
client.DefaultRequestHeaders.Add("Connection", "keep-alive");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
public async Task<Result> DoSomeWork()
{
var post = new List<KeyValuePair<string, string>>();
post.Add(new KeyValuePair<string, string>("key", "value")); //"key" "value" refers to real
var content = new FormUrlEncodedContent(post);
try
{
var response = await client.PostAsync("requestUri", content);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsAsync<Result>();
}
catch (HttpRequestException e)
{
return null;
}
}
}
In another UI class,
Service service = new Service();
it also contains a timer which interval is 1 second. In its Tick Handler method, some code like this:
async void testTimer_Tick(object sender, EventArgs e)
{
await service.DoSomeWork();
}
In this program, I want to post data to a website every second. I find that many requests are pending. If 10 minutes passed, I want to start another Task immediately. How can I cancel some Tasks before?
If I use CancellationTokenSource class,I find it also can't cancel so many tasks immediately. Is there a method like Thread.Abort() to abort a thread? Or how can I complete this program in another way? Thank you.
If I use CancellationTokenSource class,I find it also can't cancel so many tasks immediately.
Sure it can. A single CancellationTokenSource cancels its own CancellationToken, and that same CancellationToken can be copied between multiple operations. CancellationTokenSource is exactly what you should use.
Some of the higher-level HttpClient methods (such as GetStringAsync) do not support CancellationToken out of the box, but Lucian Wischik has a good blog post on adding that support.

Disposing CancellationTokenRegistrations

Contrived example, but suppose I have the following in an async method:
var cts = new CancellationTokenSource();
cts.CancelAfter(2000);
cts.Token.Register(Callback);
SomethingThatMightThrow();
await Task.Delay(10000, cts.Token);
This works as expected insofar as after a couple of seconds Callback is called. However, I want to dispose the registration after the Task.Delay, so suppose I make the following modification:
var cts = new CancellationTokenSource();
cts.CancelAfter(2000);
using (cts.Token.Register(Callback))
{
SomethingThatMightThrow();
await Task.Delay(10000, cts.Token);
}
In this case, Callback is not called, presumably because the exception from the await Task.Delay... causes the registration to be disposed before it gets invoked.
What's the best way of ensuring both that Callback gets called on cancellation and that the registration always gets disposed?
I did think of the following, but I'm not sure how robust it is:
var cts = new CancellationTokenSource();
cts.CancelAfter(2000);
var ctr = cts.Token.Register(Callback);
try
{
SomethingThatMightThrow();
await Task.Delay(10000, cts.Token);
}
finally
{
if (!cts.Token.IsCancellationRequested)
ctr.Dispose();
}
CancellationToken.Register is generally used to interop the new CancellationToken system with an older system that uses some other kind of notification for cancellation. It is not intended for use as a general-purpose "cancellation callback".
If you want to respond some way when an operation is canceled, then you just catch the appropriate exception:
using (var cts = new CancellationTokenSource())
{
cts.CancelAfter(2000);
SomethingThatMightThrow();
try
{
await Task.Delay(10000, cts.Token);
}
catch (OperationCanceledException)
{
Callback();
}
}
it's not so much that the method might not throw the exception, as
that it might not throw it as quickly as I'd like. I've noticed some
of the Azure SDK async methods, for example, can take quite a while to
respond to cancellation being signalled on the token
As per you comment, you can choose to create your own timer to explicitly specify when a method "is taking too long to run" according to your standards. For that you can use Task.WhenAny:
using (var cts = new CancellationTokenSource())
{
try
{
var cancellationDelayTask = Task.Delay(2000, cts.Token);
var taskThatMightThrow = SomethingThatMightThrowAsync(cts.Token);
if ((await Task.WhenAny(taskThatMightThrow, cancellationDelayTask))
== cancellationDelayTask)
{
// Task.Delay "timeout" finished first.
}
}
catch (OperationCanceledException)
{
Callback();
}
}

Multi-Threading Cross-Class Cancellation with TPL

All, I have a long running process that I run on a background thread (with cancellation support) using the Task Paralell Library (TPL). The code for this long running taks is contained within Class Validation, and when the method
public bool AsyncRunValidationProcess(TaskScheduler _uiScheduler,
CancellationToken _token, dynamic _dynamic = null)
{
try
{
// Note: _uiScheduler is used to update the UI thread with progress infor etc.
for (int i = 0; i < someLargeLoopNumber; i++)
{
// Cancellation requested from UI Thread.
if (_token.IsCancellationRequested)
_token.ThrowIfCancellationRequested();
}
return true;
}
catch (Exception eX)
{
// Do stuff. Display `eX` if cancellation requested.
return false;
}
}
is run from Class Validation I can cancel the process fine. The cancellation request is handled by the appropriate delegate (shown below) and this works fine (I don't belive this is the cause of my problem).
When I run this method from another class, Class Batch, I do this via a "controller" method
asyncTask = Task.Factory.StartNew<bool>(() => asyncControlMethod(), token);
which in turn invokes the method
valForm.AsyncRunValidationProcess(uiScheduler, token,
new List<string>() { strCurrentSiteRelPath }));
where valForm is my accessor to Class Validation, the method runs fine, but when I attempt a cancellation the delegate
cancelHandler = delegate
{
UtilsTPL.CancelRunningProcess(asyncTask, cancelSource);
};
where
public static void CancelRunningProcess(Task _task,
CancellationTokenSource _cancelSource)
{
try
{
_cancelSource.Cancel();
_task.Wait(); // On cross-class call it freezes here.
}
catch (AggregateException aggEx)
{
if (aggEx.InnerException is OperationCanceledException)
Utils.InfoMsg("Operation cancelled at users request.");
if (aggEx.InnerException is SqlException)
Utils.ErrMsg(aggEx.Message);
}
}
freezes/hangs (with no unhandled exception etc.) on _task.Wait(). This (I belive - through testing) is to do with the fact that I am cancelling asyncControlMethod() which has called valForm.AsyncRunValidationProcess(...), so it is cancelling asyncControlMethod() which is causing the current process to hang. The problem seems to be with passing the CancellationTokenSource etc. to the child method. The IsCancellationPending event fires and kills the controlling method, which causes the child method to hang.
Can anyone tell me what I am doing wrong or (more pertinently), what should I be doing to allow such a cancellation procedure?
Note: I have tried to spawn a child task to run valForm.AsyncRunValidationProcess(...), with its own CancellationToken but this has not worked.
Thanks for your time.
The answer to this problem (helped massively by Jiaji Wu's comment and link) was that you cannot declare the CancellationToken as a global variable that is passed to the cascading methods; that is, you cannot have
public class MainClass
{
private CancellationTokenSource = source;
private CancellationToken token;
public MainClass()
{
source = new CancellationtokenSource();
token = source.Token;
}
private void buttonProcessSel_Click(object sender, EventArgs e)
{
// Spin-off MyMethod on background thread.
Task<bool> asyncControllerTask = null;
TaskSpin(asyncControllerTask, cancelSource, token, MyMethod);
}
private void method()
{
// Use the global token DOES NOT work!
if (token.IsCancellationRequested)
token.ThrowIfCancellationRequested();
}
private void TaskSpin(Task<bool> asyncTask, CancellationTokenSource cancelSource,
CancellationToken token, Func<bool> asyncMethod)
{
try
{
token = cancelSource.Token;
asyncTask = Task.Factory.StartNew<bool>(() => asyncMethod(token), token);
// To facilitate multitasking the cancelTask ToolStripButton
EventHandler cancelHandler = null;
if (cancelSource != null)
{
cancelHandler = delegate
{
UtilsTPL.CancelRunningProcess(mainForm, uiScheduler, asyncTask, cancelSource, true);
};
}
// Callback for finish/cancellation.
asyncTask.ContinueWith(task =>
{
// Handle cancellation etc.
}
// Other stuff...
}
}
}
Use of the global token in the maethod run on the background thread doen NOT work! The method must be explicitly passed the token for it to be able to register it. I am not sure of the exact reason why this is the case, but I will know in future, now you need to pass the token to MyMethod() like this
private void method(CancellationToken token)
{
// Use the global token DOES NOT work!
if (token.IsCancellationRequested)
token.ThrowIfCancellationRequested();
}
I hope this helps someone else.

Categories