Cancelling a Long Running Task Delay - c#

I am trying to cancel a async delay task (Task.Delay) that was created by a web api request, with an another web api request that issues cancel. It does not look like it cancelled the timer for task.delay. Here is the code snippet that I am trying to implement. For your information I am using Application object to store the CancellationTokenSource object to retrieve the token source across multiple requests.
Update Question is I am expecting the task to be cancelled by throwing an exception from the code. But it never happened. How do I make this code to cancel the task.delay?
using Microsoft.Practices.Unity;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
namespace WebApplication8.Controllers
{
public class TaskController : ApiController
{
[HttpGet]
public async Task<string> CreateTask(int id)
{
var tokenSource = new CancellationTokenSource();
var concurrentTokens = GetConcurrentTokens();
concurrentTokens.TryAdd(id, tokenSource);
CancellationToken token = tokenSource.Token;
token.ThrowIfCancellationRequested();
await Task.Delay(50000,token);
return "Task Created";
}
[HttpGet]
public async Task<string> Cancel(int id)
{
var concurrentTokens = GetConcurrentTokens();
CancellationTokenSource item = concurrentTokens.First(t => t.Key == id).Value;
item.Cancel();
item.Dispose();
var tokenSource2 = new CancellationTokenSource();
concurrentTokens.TryRemove(id,out tokenSource2);
return "Cancelled";
}
private ConcurrentDictionary<long, CancellationTokenSource> GetConcurrentTokens()
{
var tokens = HttpContext.Current.Application["Tokens"];
if (tokens == null)
{
tokens = new ConcurrentDictionary<long, CancellationTokenSource>();
HttpContext.Current.Application["Tokens"] = tokens;
}
return (ConcurrentDictionary<long, CancellationTokenSource>) tokens;
}
}
}

I think it get cancelled , you can try it by adding try catch like this:
try
{
await Task.Delay(5000, token);
}
catch(TaskCanceledException ex)
{
}
And you will see it enters the catch block, the method doesn't return any thing because TaskCanceledException

Your code looks correct, I tested it like this:
var tc = new TaskController();
var backTask1 = tc.CreateTask(1);
var backTask2 = tc.CreateTask(2);
// task 2 gets cancelled
await tc.Cancel(2);
try
{
var res2 = await backTask2;
}
catch (OperationCanceledException) { }
// task 1 waits
await backTask1;
n.b. that:
token.ThrowIfCancellationRequested() does nothing just after you created the CancellationTokenSource - this method literally just throws an exception if some code has cancelled the source already.
If a task is started without being awaited, you won't see exceptions it raised until it is awaited.

Try using TaskCompletionSource. It works for me, but I'm not sure if this is what you are looking for.
var source = new CancellationTokenSource();
var concurrentTokens = GetConcurrentTokens();
concurrentTokens.TryAdd(id, source);
var completionSource = new TaskCompletionSource<object>();
source.Token.Register(() => completionSource.TrySetCanceled());
var task = Task.Delay(50000, source.Token);
// Continue when any of these are done.
await Task.WhenAny(task, completionSource.Task);
if (task.IsCanceled)
{
return "Task was not created";
}
return "Task Created";
Also, there's no need to keep Cancel async. You can return a string instead of Task<string>.

Related

Point of Task.Run if it will be awaited immediately

I have this method:
public async Task DoSomething()
{
string token = string.Empty;
await Task.Run(() =>
{
using (var sc = _dbManager.CreateSession())
{
// somework...
}
});
using (var resp = await client.PostAsync(authenticationEndpoint,
new FormUrlEncodedContent(authenticateRequest), stoppingToken))
{
resp.EnsureSuccessStatusCode();
string stringResponse = await resp.Content.ReadAsStringAsync(stoppingToken);
var response = JsonConvert.DeserializeObject<Response>(stringResponse);
token = response.AccessToken;
}
}
Now I do not understand why I need to use await Task.Run? It's confusing for me to understand logic behind this, as I'm awaiting the task to finish anyway, so why should I put it in the Task in the first place?
My lead told me that this was the way to go, and also explained to me that if I don't put that in Task, the code will be executed again from the top, after await client.PostAsync.

How to get effect of Task.WhenAny for a Task and CancellationToken?

I have interactive task which in "worst" scenario is not executed at all, thus it is represented by TaskCompletionSource.
I would like to wait for either this task completes, or token which I received is cancelled -- whichever happens first. Perfect tool for such job would be Task.WhenAny, the only problem is it takes only tasks, and I have one Task and one CancellationToken.
How to wait (asynchronously, like Task.WhenAny) for the first event triggered -- completed task, or cancelled token?
async Task MyCodeAsync(CancellationToken token)
{
var tcs = new TaskCompletionSource<UserData>(); // represents interactive part
await Task.WhenAny(tcs.Task, token); // imaginary call
UserData data = tcs.Task.Result; // user interacted, let's continue
...
}
I don't create/manage token, so I cannot change it. I have to deal with it.
Update: For such particular case one could use Register method on token to cancel the TaskCompletionSource. For more general method please see Matthew Watson answer.
You could just create an extra task that returns when the cancel token's wait handle is signalled:
var factory = new CancellationTokenSource();
var token = factory.Token;
await Task.WhenAny(
Task.Run(() => token.WaitHandle.WaitOne()),
myTask());
(However, be aware that this - while simple - does use up an extra thread, which is clearly not ideal. See later for an alternate solution which doesn't use an extra thread.)
If you want to check which task completed, you will have to keep a copy of the tasks before calling WhenAny() so you can compare them to the return value, for example:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
static async Task Main()
{
var factory = new CancellationTokenSource(1000); // Change to 3000 for different result.
var token = factory.Token;
var task = myTask();
var result = await Task.WhenAny(
Task.Run(() => token.WaitHandle.WaitOne()),
task);
if (result == task)
Console.WriteLine("myTask() completed");
else
Console.WriteLine("cancel token was signalled");
}
static async Task myTask()
{
await Task.Delay(2000);
}
}
}
If you don't want to waste an entire thread waiting for the cancellation token to be signalled, you can use CancellationToken.Register() to register a callback with which you can set the result of a TaskCompletionSource:
(Lifted from here)
public static Task WhenCanceled(CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<bool>();
cancellationToken.Register(s => ((TaskCompletionSource<bool>) s).SetResult(true), tcs);
return tcs.Task;
}
You can then use that as follows:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
static async Task Main()
{
var factory = new CancellationTokenSource(1000);
var token = factory.Token;
var task = myTask();
var result = await Task.WhenAny(
WhenCanceled(token),
task);
if (result == task)
Console.WriteLine("myTask() completed");
else
Console.WriteLine("cancel token was signalled");
}
public static Task WhenCanceled(CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<bool>();
cancellationToken.Register(s => ((TaskCompletionSource<bool>) s).SetResult(true), tcs);
return tcs.Task;
}
static async Task myTask()
{
await Task.Delay(2000);
}
}
}
This is a preferable approach for the general case.
With this scenario, you have to be extremely careful about leaks. In particular, having objects referenced by delegates that are registered to a long-lived CancellationToken.
The approach that I eventually ended up with in my AsyncEx library looks like this:
public static async Task<T> WaitAsync<T>(this Task<T> task, CancellationToken token)
{
var tcs = new TaskCompletionSource<T>();
using (token.Register(() => tcs.TrySetCanceled(token), useSynchronizationContext: false)
return await await Task.WhenAny(task, tcs.Task).ConfigureAwait(false);
}
The code above ensures that the registration is disposed if the CancellationToken is not canceled.
Usage:
async Task MyCodeAsync(CancellationToken token)
{
UserData data = await userDataTask.WaitAsync(token);
}
Here is an extension method that transforms a CancellationToken to a Task or Task<TResult>. The returned task will complete as cancelled immediately after the CancellationToken receives a cancellation request.
static class CancellationTokenExtensions
{
public static Task AsTask(this CancellationToken token)
{
return new Task(() => throw new InvalidOperationException(), token);
}
public static Task<TResult> AsTask<TResult>(this CancellationToken token)
{
return new Task<TResult>(() => throw new InvalidOperationException(), token);
}
}
Usage example. Just await any task:
await Task.WhenAny(tcs.Task, token.AsTask());
...or await and get the result in the same line as well:
var data = await Task.WhenAny(tcs.Task, token.AsTask<UserData>()).Unwrap();
The InvalidOperationException is thrown just in case, to ensure that the task of the CancellationToken will never run to completion. Its Status can only be Created, Canceled or Faulted.

Code using async/await fails when an HttpClient request is sent

I have a WinForms application that has two roles. If no command line parameters are present, the Main function calls Application.Run, and presents the UI. If command line parameters are present, Application.Run is NOT called. Instead, I call an async method like this:
result = HandleCommandLine(args).GetAwaiter().GetResult();
(I am new to async/await, and this form was based on a SO answer).
The end goal is to loop through a list, and for each entry, start a new task. Each of those tasks should run in parallel with the others. The tasks are started like this:
runningTasks.Add(Task.Factory.StartNew((args) => HandlePlayback( (Dictionary<string,string>) ((object[])args)[0]), new object[] { runArgs } ));
The tasks are added to the collection of runningTasks, and I later call:
Task.WaitAll(runningTasks.ToArray());
In each of the runningTasks, I am trying to send web requests using HttpClient:
using (HttpResponseMessage response = await Client.SendAsync(message))
{
using (HttpContent responseContent = response.Content)
{
result = await responseContent.ReadAsStringAsync();
}
}
Once Client.SendAsync is called, the whole thing goes belly up. All of my runningTasks complete, and the application exits. Nothing past the Client.SendAsync executes in any of those tasks.
Since I am new at async/await, I have very few ideas about what exactly might be wrong, and hence few ideas about how to fix it. I imagine it has something to do with the SynchronizationContexts in this situation (WinForms app acting like a console app), but I'm not grasping what I need to do and where to keep the service request and the web request async calls from causing everything to complete too early.
I guess my question then is, why are (only some) 'awaited' calls causing all tasks to complete? What can I do about it?
UPDATE:
Two things. #Joe White: The WindowsFormsSynchronizationContext.Current is always null wherever I check.
#David Pine: Minimal (kind of :) ) complete viable example follows. You will either need to add a command line argument to the project, or force execution to the HandleCommandLine function. In this example, it tries to make a website request for each of three sites. It doesn't appear to matter if they exist. The code reaches the Client.SendAsync some number of times (usually not three), but timing appears to matter.
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
static class Program
{
static List<Task> runningTasks = new List<Task>();
[STAThread]
static int Main()
{
int result = 1; // true, optimism
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
string[] args = Environment.GetCommandLineArgs();
if (args.Length > 1)
{
// do the command line work async, while keeping this thread active
result = HandleCommandLine(args).GetAwaiter().GetResult();
}
else
{
// normal interface mode
Application.Run(new Form1());
}
return result;
}
static async Task<int> HandleCommandLine(string[] args)
{
// headless mode
int result = 1; // true, optimism
result = await HandleControlMode(args);
return result;
}
private static async Task<int> HandleControlMode(string[] Arguments)
{
int result = 1; // optimism
try
{
List<string> sites = new List<string>() { #"http://localhost/site1", #"http://localhost/site2", #"http://localhost/site3" };
foreach (string site in sites)
{
Begin(site); // fire off tasks
// the HandleControlMode method is async because in other circumstances, I do the following:
//await Task.Delay(5000); // sleep 5 seconds
}
// wait while all test running threads complete
try
{
Task.WaitAll(runningTasks.ToArray());
}
catch (Exception)
{
// not really a catch all handler...
}
}
catch (Exception)
{
// not really a catch all handler...
}
return result;
}
private static void Begin(string site)
{
//runningTasks.Add(Task.Factory.StartNew(() => HandlePlayback(runArgs)));
runningTasks.Add(Task.Factory.StartNew((args) => HandlePlayback((string)((object[])args)[0]), new object[] { site }));
}
private static async Task<int> HandlePlayback(string site)
{
int result = 1;
try
{
PlaybackEngine engine = new PlaybackEngine(site);
bool runResult = await engine.RunCommandLine(site);
if (!runResult)
{
result = 0;
}
}
catch (Exception)
{
result = 0;
}
return result;
}
}
public class PlaybackEngine
{
private static HttpClientHandler ClientHandler = new HttpClientHandler()
{
AllowAutoRedirect = false,
AutomaticDecompression = System.Net.DecompressionMethods.GZip | DecompressionMethods.Deflate
};
private static HttpClient Client = new HttpClient(ClientHandler);
public string Target { get; set; }
public PlaybackEngine(string target)
{
Target = target;
}
public async Task<bool> RunCommandLine(string site)
{
bool success = true;
string response = await this.SendRequest();
return success;
}
private async Task<string> SendRequest()
{
string result = string.Empty;
string requestTarget = Target;
HttpMethod method = HttpMethod.Post;
var message = new HttpRequestMessage(method, requestTarget);
StringContent requestContent = null;
requestContent = new StringContent("dummycontent", Encoding.UTF8, "application/x-www-form-urlencoded");
message.Content = requestContent;
try
{
using (HttpResponseMessage response = await Client.SendAsync(message))
{
using (HttpContent responseContent = response.Content)
{
result = await responseContent.ReadAsStringAsync();
System.Diagnostics.Debug.WriteLine(result);
}
}
}
catch (Exception ex)
{
}
return result;
}
}
}
UPDATE2:
I put similar code online at http://rextester.com/CJS33330
It's a straight console app, and I've added .ConfigureAwait(false) to all awaits (with no effect). In separate testing, I tried 4 or 5 other ways to call the first async function from Main - which all worked but had the same behavior.
The problem with this code is that I am not waiting on the Tasks that I thought I was. The runningTasks collection accepts any kind of Task. I didn't realize that Task.Factory.StartNew returned different type than the Task I was trying to start. My function returns
Task<int>
but StartNew returns
Task<Task<int>>
Those tasks completed immediately, and so the main thread did not stay alive long enough for the actual routines to run. You have to wait on the inner task instead:
Task<Task<int>> wrappedTask = Task.Factory.StartNew(...);
Task<int> t = await wrappedTask;
runningTasks.Add(t);
...
Task allTasks = Task.WhenAll(runningTasks.ToArray());
await allTasks;
For some reason, I was not able to use the built in ".Unwrap" function that should be equivalent, but the above code does the job.

ContinueWith not being called async

I'm trying to httpget some values before I execute the next line in the statement. I need to wait for this call to return so I can use the values I deserialize into a list.
Since I want the async call to finish first, I wrapped this in a Task. It worked and it's successfully retrieving the JSON. I then can't get it to go into the ContinueWith block. Why is it not going in there, even when the task is completed(?).
How I'm calling it:
Task f = Task.Run(() =>
{
var task = RetrieveDataAsync();
}).ContinueWith((antecedent) =>
{
pokemonListActivityListView.Adapter = new PokemonListAdapter(this, pokemonList);
pokemonListActivityListView.FastScrollEnabled = true;
pokemonListActivityListView.ItemClick += PokemonListActivityListViewOnItemClick;
});
RetrieveDataAsync method:
private async Task RetrieveDataAsync()
{
string dataUri = "http://pokemonapp6359.azurewebsites.net/Pkmn/GetAllPokemon";
using (var httpClient = new HttpClient())
{
var uri = new Uri(string.Format(dataUri, string.Empty));
//DisplayProgressBar(BeforeOrAfterLoadState.Before, progressBarView);
var response = await httpClient.GetAsync(uri);
//DisplayProgressBar(BeforeOrAfterLoadState.After, progressBarView);
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
pokemonList = JsonConvert.DeserializeObject<List<PokemonDTO>>(content);
//canPressButtons = true; //fix this when implement local db
Utilities.Utilities.ShowToast(this, "Successfully fetched data", ToastLength.Short, GravityFlags.Center);
return;
}
else
{
Utilities.Utilities.ShowToast(this, "Failed to fetch data", ToastLength.Short, GravityFlags.Center);
return;
}
}
}
Why is my code not going into the ContinueWith when I've got the JSON ? Thanks!
Instead of just assigning the hot task, you are not waiting on it to finish. You have to call ContinueWith on that task:
var task = RetrieveDataAsync();
task.ContinueWith( ... );
Or await the task:
var result = await RetrieveDataAsync();
... // continue
The problem is that you're ignoring the task returned from RetrieveDataAsync. If you return that task from your lambda expression, then it will behave as you expect.
On a side note, you shouldn't use ContinueWith; it's a dangerous API. Use await instead of ContinueWith:
await Task.Run(() => RetrieveDataAsync());
pokemonListActivityListView.Adapter = new PokemonListAdapter(this, pokemonList);
pokemonListActivityListView.FastScrollEnabled = true;
pokemonListActivityListView.ItemClick += PokemonListActivityListViewOnItemClick;

How to cancel other threads as soon as one completed thread satisfies condition

I have an ASP.NET MVC application which needs to check if something exists at 3 remote API servers. The application passes an ID to each API and it returns either true or false. The code looks like this.
public class PingController
{
public async Task<bool> IsFound(int id)
{
var servers = new ['a.com', b.com', 'c.com'];
var result = await foundAtServers(id, servers);
return result;
}
private async Task<bool> foundAtServers(int id, string[] servers)
{
var tasks = from server in servers
select checkServer(id, server);
return await.Task.WhenAll(tasks.ToArray());
}
private async Task<bool> checkServer(id, server)
{
var request = new HttpRequestMessage(HttpMethod.Get, server+"/api/exists"+id);
var client = new HttpClient();
var task = await client.SendAsync(request);
var response = await task.Content.ReadAsStringAsync();
return bool.Parse(response);
}
}
This code currently checks all 3 APIs asynchronously but will wait until ALL of the HttpClient calls have completed before the MVC Action can return.
As soon as one API returns true I want to immediately return true on the Action, rather than wait for the other tasks to complete.
The C# Task class has .WaitAll and .WaitAny, but these won't work either. As I need to cancel the other HttpClient request, I presume I need to use a CancellationToken but I don't know how to use it with this structure.
Cheers.
If you want to immediately return, you can use Task.WhenAny instead of Task.WhenAll. This won't cancel the on-going tasks, but it will enable you to return as soon as possible:
private async Task<bool> FoundAtServersAsync(int id, string[] servers)
{
var tasks = (from server in servers
select checkServer(id, server)).ToList();
while (tasks.Count > 0)
{
var finishedTask = await Task.WhenAny(tasks);
if (finishedTask.Result)
{
return finishedTask.Result;
}
tasks.Remove(finishedTask);
}
return false;
}
This will discard the other tasks. This means that if any exception is thrown inside one of them, it will be swallowed.
Edit:
If you care about actually canceling the other tasks, consider passing your CancellationToken to the overload of SendAsync which takes one, and calling CancellationTokenSource.Cancel once a value is received. Note this will mean you'll also need to handle the OperationCanceledException they will throw.
If they don't matter, i'd simply discard them as above.
This problem is made easier by using the following method to take a sequence of tasks and order them based on when they are completed.
public static IEnumerable<Task<T>> Order<T>(this IEnumerable<Task<T>> tasks)
{
var taskList = tasks.ToList();
var taskSources = new BlockingCollection<TaskCompletionSource<T>>();
var taskSourceList = new List<TaskCompletionSource<T>>(taskList.Count);
foreach (var task in taskList)
{
var newSource = new TaskCompletionSource<T>();
taskSources.Add(newSource);
taskSourceList.Add(newSource);
task.ContinueWith(t =>
{
var source = taskSources.Take();
if (t.IsCanceled)
source.TrySetCanceled();
else if (t.IsFaulted)
source.TrySetException(t.Exception.InnerExceptions);
else if (t.IsCompleted)
source.TrySetResult(t.Result);
}, CancellationToken.None,
TaskContinuationOptions.PreferFairness,
TaskScheduler.Default);
}
return taskSourceList.Select(tcs => tcs.Task);
}
With this you can write:
public static async Task<bool> WhenAny(this IEnumerable<Task<bool>> tasks)
{
foreach (var task in tasks.Order())
if (await task)
return true;
return false;
}
You could wait for the first task to complete - if it's successful, return true immediately. Otherwise, wait for the next one to complete, and so on and so forth.
private async Task<bool> foundAtServers(int id, string[] servers)
{
var tasks = servers.Select(server => checkServer(id, server))
.ToList();
while(tasks.Any())
{
var task = await Task.WhenAny(tasks);
if(task.Result)
return true;
tasks.Remove(task);
}
return false;
}

Categories