I need to implement async task cancel. I know that CancellationTokenSource would help me in achieving that. But I'm unable to find a proper way.
I have a search textbox, whenever a user types in textbox, for each textchanged event I call GetStocks method as shown below,
public async Task GetStocks()
{
var stockings = new List<Services.Models.Admin.SiteStockingLevelsModel>();
IsBusy = true;
cts?.Cancel();
cts = new CancellationTokenSource();
await Task.Run(() => { CreateStockingCollection(); });
ValidateMaterials();
IsBusy = false;
}
The CreateStockingCollection method is as shown below,
private void CreateStockingCollection()
{
var stockings = _siteStockingLevelsService.GetSiteInventoryLevels(SiteId);
CreateStockingLevelCompareCollection(stockings);
StockingLevels =
_mapper.Map<TrulyObservableCollection<SiteStockingLevelsModel>>(stockings);
((INotifyPropertyChanged)StockingLevels).PropertyChanged +=
(x, y) => CompareStockingChanges();
CompareStockingChanges();
}
My requirement here is,
Example Suppose user wants to type "Abc". When user types "A" the GetStocks method will be called, immediately the user enters "b" the again the get stocks methods will be called, in this case i want to cancel the previous GetStocks task called with letter "A".
Cancellation is cooperative, so you you need to pass it to your own code and have it respond to that token:
public async Task GetStocks()
{
var stockings = new List<Services.Models.Admin.SiteStockingLevelsModel>();
IsBusy = true;
cts?.Cancel();
cts = new CancellationTokenSource();
var token = cts.Token;
await Task.Run(() => { CreateStockingCollection(token); });
ValidateMaterials();
IsBusy = false;
}
private void CreateStockingCollection(CancellationToken token)
{
var stockings = _siteStockingLevelsService.GetSiteInventoryLevels(SiteId, token);
CreateStockingLevelCompareCollection(stockings);
StockingLevels =
_mapper.Map<TrulyObservableCollection<SiteStockingLevelsModel>>(stockings);
((INotifyPropertyChanged)StockingLevels).PropertyChanged +=
(x, y) => CompareStockingChanges();
CompareStockingChanges();
}
Here I'm passing it to GetSiteInventoryLevels, which sounds like that would be the long-running part of this work. GetSiteInventoryLevels must now take the CancellationToken and pass it along to whatever APIs it's using.
One of the best practices of the async programming in .Net is Async all the way. Looks like your method isn't async-based, neither does it accept CancellationToken. You need to add it to your method, in other way only Task.Run will try to cancel your task, which wouldn't work well.
Also, the only creation of the CancellationTokenSource isn't enough - you need to use it's .Token property in your code - this would exactly that token:
await Task.Run(() => { CreateStockingCollection(); }, cts.Token);
Related
ITNOA
Hi,
I have a UWP project and I want to message passing between ViewModel and View with CommunityToolkit MVVM Toolkit.
As you can see in my ViewModel we have something like below
private async void CallbackClick()
{
while (true)
{
CallbackDto callback = callbackQueue.Pop();
if (callback is null)
break;
callbackQueue.Push(new CallbackDto(callback.Number, DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
string normalizedAddress = callback.Number;
BSN.LinphoneSDK.Call outgoingCall = await LinphoneManager.Instance.NewOutgoingCall($"{normalizedAddress}");
await outgoingCall.WhenEnded();
// TODO: It is mandatory for backing to Dialer from InCall, but it is very bugous and must fix it
await Task.Delay(1000);
Task<CancellationToken> cancellationTokenTask = WeakReferenceMessenger.Default.Send<ContinueCallbackAnsweringRequestMessage>();
CancellationToken cancellationToken = await cancellationTokenTask;
if (cancellationToken.IsCancellationRequested)
break;
}
}
And I have register on message in Dialer.xaml.cs in NavigatedTo like below
WeakReferenceMessenger.Default.Register<ContinueCallbackAnsweringRequestMessage>(this, async (r, message) =>
{
var continueCallbackAnsweringDialog = new MessageDialog("آیا مایل به ادامه پاسخدهی به تماسهای درخواستی هستید؟");
TaskCompletionSource<CancellationToken> tcs = new TaskCompletionSource<CancellationToken>(TaskCreationOptions.RunContinuationsAsynchronously);
continueCallbackAnsweringDialog.Commands.Add(new UICommand(
"بلی",
new UICommandInvokedHandler((IUICommand command) =>
{
tcs.SetResult(new CancellationToken(false));
})));
continueCallbackAnsweringDialog.Commands.Add(new UICommand(
"خیر",
new UICommandInvokedHandler((IUICommand command) =>
{
tcs.SetResult(new CancellationToken(true));
})));
continueCallbackAnsweringDialog.DefaultCommandIndex = 0;
continueCallbackAnsweringDialog.CancelCommandIndex = 1;
await continueCallbackAnsweringDialog.ShowAsync();
message.Reply(tcs.Task);
});
In this scenario I got a InvalidOperationException on WeakReferenceMessenger.Default.Send. My question is how to handle this situation?
In another side, if I remove async and await keyword in handler, my code is work correctly. but the problem is I do not await on IAsyncOperation and I want to await on in it.
I add discussion on CommunityToolkit GitHub about that.
My question is how to handle this situation?
Since the MessageHandler won't (can't) be awaited by the toolkit, it returns when you call await ....
You could try something like this:
WeakReferenceMessenger.Default.Register<ContinueCallbackAnsweringRequestMessage>(
this, (r, m) => m.Reply(ShowDialogAndWaitForResult()));
...where ShowDialogAndWaitForResult() is a custom async method that returns a Task<T>, calls the ShowAsync() of the dialog and wait for the result.
Another option is implement your own blocking (non-async) dialog. Or consider another solution which don't involve a messenger.
I was recently exposed to C# language and was working on getting data out of cassandra so I was working with below code which gets data from Cassandra and it works fine.
Only problem I have is in my ProcessCassQuery method - I am passing CancellationToken.None to my requestExecuter Function which might not be the right thing to do. What should be the right way to handle that case and what should I do to handle it correctly?
/**
*
* Below method does multiple async calls on each table for their corresponding id's by limiting it down using Semaphore.
*
*/
private async Task<List<T>> ProcessCassQueries<T>(IList<int> ids, Func<CancellationToken, int, Task<T>> mapperFunc, string msg) where T : class
{
var tasks = ids.Select(async id =>
{
await semaphore.WaitAsync();
try
{
ProcessCassQuery(ct => mapperFunc(ct, id), msg);
}
finally
{
semaphore.Release();
}
});
return (await Task.WhenAll(tasks)).Where(e => e != null).ToList();
}
// this might not be good idea to do it. how can I improve below method?
private Task<T> ProcessCassQuery<T>(Func<CancellationToken, Task<T>> requestExecuter, string msg) where T : class
{
return requestExecuter(CancellationToken.None);
}
As said in the official documentation, the cancellation token allows propagating a cancellation signal. This can be useful for example, to cancel long-running operations that for some reason do not make sense anymore or that are simply taking too long.
The CancelationTokenSource will allow you to get a custom token that you can pass to the requestExecutor. It will also provide the means for cancelling a running Task.
private CancellationTokenSource cts = new CancellationTokenSource();
// ...
private Task<T> ProcessCassQuery<T>(Func<CancellationToken, Task<T>> requestExecuter, string msg) where T : class
{
return requestExecuter(cts.Token);
}
Example
Let's take a look at a different minimal/dummy example so we can look at the inside of it.
Consider the following method, GetSomethingAsync that will yield return an incrementing integer every second.
The call to token.ThrowIfCancellationRequested will make sure a TaskCanceledException is thrown if this process is cancelled by an outside action. Other approaches can be taken, for example, check if token.IsCancellationRequested is true and do something about it.
private static async IAsyncEnumerable<int> GetSomethingAsync(CancellationToken token)
{
Console.WriteLine("starting to get something");
token.ThrowIfCancellationRequested();
for (var i = 0; i < 100; i++)
{
await Task.Delay(1000, token);
yield return i;
}
Console.WriteLine("finished getting something");
}
Now let's build the main method to call the above method.
public static async Task Main()
{
var cts = new CancellationTokenSource();
// cancel it after 3 seconds, just for demo purposes
cts.CancelAfter(3000);
// or: Task.Delay(3000).ContinueWith(_ => { cts.Cancel(); });
await foreach (var i in GetSomethingAsync(cts.Token))
{
Console.WriteLine(i);
}
}
If we run this, we will get an output that should look like:
starting to get something
0
1
Unhandled exception. System.Threading.Tasks.TaskCanceledException: A task was canceled.
Of course, this is just a dummy example, the cancellation could be triggered by a user action, or some event that happens, it does not have to be a timer.
Good day! I am writing a helper library for WinForms UI. Started using TPL async/await mechanism and got a problem with this kind of code example :
private SynchronizationContext _context;
public void UpdateUI(Action action)
{
_context.Post(delegate { action(); }, null);
}
private async void button2_Click(object sender, EventArgs e)
{
var taskAwait = 4000;
var progressRefresh = 200;
var cancellationSource = new System.Threading.CancellationTokenSource();
await Task.Run(() => { UpdateUI(() => { button2.Text = "Processing..."; }); });
Action usefulWork = () =>
{
try
{
Thread.Sleep(taskAwait);
cancellationSource.Cancel();
}
catch { }
};
Action progressUpdate = () =>
{
int i = 0;
while (i < 10)
{
UpdateUI(() => { button2.Text = "Processing " + i.ToString(); });
Thread.Sleep(progressRefresh);
i++;
}
cancellationSource.Cancel();
};
var usefulWorkTask = new Task(usefulWork, cancellationSource.Token);
var progressUpdateTask = new Task(progressUpdate, cancellationSource.Token);
try
{
cancellationSource.Token.ThrowIfCancellationRequested();
Task tWork = Task.Factory.StartNew(usefulWork, cancellationSource.Token);
Task tProgress = Task.Factory.StartNew(progressUpdate, cancellationSource.Token);
await Task.Run(() =>
{
try
{
var res = Task.WaitAny(new[] { tWork, tProgress }, cancellationSource.Token);
}
catch { }
}).ConfigureAwait(false);
}
catch (Exception ex)
{
}
await Task.Run(() => { UpdateUI(() => { button2.Text = "button2"; }); });
}
Basically, the idea is to run two parallel tasks - one is for, say, progress bar or whatever update and a sort of timeout controller, the other is the long running task itself. Whichever task finishes first cancels the other one. So, there should not be a problem to cancel the "progress" task as it has a loop in which I can check if task is marked cancelled. The problem is with the long running one. It could be Thread.Sleep() or SqlConnection.Open(). When I run CancellationSource.Cancel(), the long running task keeps working and does not cancel. After a timeout I am not interested in long running task or whatever it may result in.
As the cluttered code example may suggest, I have tried a bunch of variants and none given me a desired effect. Something like Task.WaitAny() freezes UI... Is there a way to make that cancellation work or may be even a different approach to code these things?
UPD:
public static class Taskhelpers
{
public static async Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<bool>();
using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs))
{
if (task != await Task.WhenAny(task, tcs.Task))
throw new OperationCanceledException(cancellationToken);
}
return await task;
}
public static async Task WithCancellation(this Task task, CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<bool>();
using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs))
{
if (task != await Task.WhenAny(task, tcs.Task))
throw new OperationCanceledException(cancellationToken);
}
await task;
}
}
.....
var taskAwait = 4000;
var progressRefresh = 200;
var cancellationSource = new System.Threading.CancellationTokenSource();
var cancellationToken = cancellationSource.Token;
var usefulWorkTask = Task.Run(async () =>
{
try
{
System.Diagnostics.Trace.WriteLine("WORK : started");
await Task.Delay(taskAwait).WithCancellation(cancellationToken);
System.Diagnostics.Trace.WriteLine("WORK : finished");
}
catch (OperationCanceledException) { } // just drop out if got cancelled
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine("WORK : unexpected error : " + ex.Message);
}
}, cancellationToken);
var progressUpdatetask = Task.Run(async () =>
{
for (var i = 0; i < 25; i++)
{
if (!cancellationToken.IsCancellationRequested)
{
System.Diagnostics.Trace.WriteLine("==== : " + i.ToString());
await Task.Delay(progressRefresh);
}
}
},cancellationToken);
await Task.WhenAny(usefulWorkTask, progressUpdatetask);
cancellationSource.Cancel();
By modifying for (var i = 0; i < 25; i++) limit of i I imitate whether long running task finishes before the progress task or otherwise. Works as desired. The WithCancellation helper method does the job, although two sort of 'nested' Task.WhenAny look suspicious for now.
I agree with all the points in Paulo's answer - namely, use modern solutions (Task.Run instead of Task.Factory.StartNew, Progress<T> for progress updates instead of manually posting to the SynchronizationContext, Task.WhenAny instead of Task.WaitAny for asynchronous code).
But to answer the actual question:
When I run CancellationSource.Cancel(), the long running task keeps working and does not cancel. After a timeout I am not interested in long running task or whatever it may result in.
There are two parts to this:
How do I write code that responds to a cancellation request?
How do I write code that ignores any responses after the cancellation?
Note that the first part deals with cancelling the operation, and the second part is actually dealing with cancelling the waiting for the operation to complete.
First things first: support cancellation in the operation itself. For CPU-bound code (i.e., running a loop), periodically call token.ThrowIfCancellationRequested(). For I/O-bound code, the best option is to pass the token down to the next API layer - most (but not all) I/O APIs can (should) take cancellation tokens. If this isn't an option, then you can either choose to ignore the cancellation, or you can register a cancellation callback with token.Register. Sometimes there's a separate cancellation method you can call from your Register callback, and sometimes you can make it work by disposing the object from the callback (this approach often works because of a long-standing Win32 API tradition of cancelling all I/O for a handle when that handle is closed). I'm not sure if this will work for SqlConnection.Open, though.
Next, cancelling the wait. This one is relatively simple if you just want to cancel the wait due to a timeout:
await Task.WhenAny(tWork, tProgress, Task.Delay(5000));
When you write something like await Task.Run(() => { UpdateUI(() => { button2.Text = "Processing..."; }); }); on your button2_Click, you are, from the UI thread, scheduling an action to a thread poll thread that posts an action to the UI thread. If you called the action directly, it would be quickier because it wouldn't have two context switchings.
ConfigureAwait(false) causes the synchronization context to not being captured. I should not be used inside UI methods because, you most certainely, want to do some UI work on the continuation.
You shouldn't use Task.Factory.StartNew instead of Task.Run unless you absolutely have a reason to. See this and this.
For progress updates, consider using the Progress<T> class, because it captures the synchronization context.
Maybe you should try something like this:
private async void button2_Click(object sender, EventArgs e)
{
var taskAwait = 4000;
var cancellationSource = new CancellationTokenSource();
var cancellationToken = cancellationSource.Token;
button2.Text = "Processing...";
var usefullWorkTask = Task.Run(async () =>
{
try
{
await Task.Dealy(taskAwait);
}
catch { }
},
cancellationToken);
var progress = new Progress<imt>(i => {
button2.Text = "Processing " + i.ToString();
});
var progressUpdateTask = Task.Run(async () =>
{
for(var i = 0; i < 10; i++)
{
progress.Report(i);
}
},
cancellationToken);
await Task.WhenAny(usefullWorkTask, progressUpdateTask);
cancellationSource.Cancel();
}
I think you need to check IsCancellationRequested in the progressUpdate Action.
As to how to do what you want, this blog discusses an Extension method WithCancellation that will make it so that you stop waiting for your long running task.
I know it has been asked a lot, but my problem is, that my method won't wait for the request to be completet, even though i have implemented a TaskCompletionSource, which should have done the job, but it doesn't.
public DecksViewModel(bool local)
{
DList = new List<Deck>();
if (local)
InitializeLocalDeckList();
else
{
Dereffering();
}
}
public async void Dereffering()
{
var e = await InitilaizeWebDeckList();
List<DeckIn> decksIn = JsonConvert.DeserializeObject<List<DeckIn>>(e);
foreach (DeckIn d in decksIn)
{
Deck dadd = new Deck();
dadd.CardCount = 0;
dadd.Name = d.name;
dadd.PicturePath = d.image;
dadd.InstallDirectory = false;
DList.Add(dadd);
}
DataSource = AlphaKeyGroup<Deck>.CreateGroups(DList, System.Threading.Thread.CurrentThread.CurrentUICulture, (Deck s) => { return s.Name; }, true);
}
public Task<String> InitilaizeWebDeckList()
{
var tcs = new TaskCompletionSource<string>();
var client = new RestClient("blabla.com");
var request = new RestRequest("");
request.AddHeader("Authorization", "Basic blabla");
client.ExecuteAsync(request, response =>
{
test = response.Content;
tcs.SetResult(response.Content);
});
return tcs.Task;
}
So when I call the DecksViewModel constructor, I asyncally try to request the data from a webserver and fill the model.
The point is, that the corresponding view "doesn't wait" for the request to fill the model, so it's displayed empty.
I use the
List<AlphaKeyGroup<Deck>> DataSource
to fill a LongListSelector via DataBinding. But DataSource isn't yet set, when it is binded.
I hope you can help
You're calling an async method without awaiting it inside the constructor. That's why "it doesn't wait" (because it has nothing to wait on).
It's usually a bad idea to call an async method inside the constructor for that reason combined with the fact that constructors can't be async.
You should redesign your solution accordingly. An option is to have an async static method that creates an instance and awaits the procedure:
public static async Task CreateInstance(bool local)
{
var model = new DecksViewModel();
if (local)
{
await InitializeLocalDeckList();
}
else
{
await Dereffering();
}
}
That would allow you to not use async void which should only be used in UI even handlers.
You can read more about other options in Stephen Cleary's blog
You are using async void, which means nobody's gonna wait for that. It's just fire and forget.
I see some misunderstanding in the async keyword here:
Your code will only wait for the result of an async method, if you use await. Otherwise that call will just start the async method, but you don't know when it is actually gonna run.
You cannot use await in constructors though.
How do I cancel all but the last/latest task? For example, suppose I have a time-consuming task that is triggered on a button click. I only want the task from the last button click to run and the previous ones to cancel. Can you show me how this is normally done?
My attempt involves storing all tasks in a list along with their cancellation token and removing them when either the task completes or is cancelled. Creating a list to store both Task and CancellationToken seems like I'm doing too much for what I would've thought is a common requirement (eg. a User does a search for something and clicks the search button multiple times. Shouldn't only the last search be made and all other cancelled?). This is a common scenario, so I'd like to know how this is normally done. What is best practice here?
async void DoStuffAsync()
{
// Store tasks in a list
if (tasksAndCancelTokens == null)
tasksAndCancelTokens = new List<Tuple<Task, CancellationTokenSource>>();
else // we have a new Get request so cancel any previous
tasksAndCancelTokens.ForEach(t => t.Item2.Cancel());
// Create Cancellation Token
var cts = new CancellationTokenSource();
// Method to run asynchonously
Func<int> taskAction = () =>
{
// Something time consuming
Thread.Sleep(5000);
if (cts.Token.IsCancellationRequested)
cts.Token.ThrowIfCancellationRequested();
return 100;
};
// Create Task
Task<int> task = Task<int>.Factory.StartNew(taskAction, cts.Token);
// Create Tuple to store task in list
var tup = new Tuple<Task, CancellationTokenSource>(task, cts);
tasksAndCancelTokens.Add(tup);
try
{
int i = await task;
}
catch (OperationCanceledException)
{
// Don't need to do anything
}
finally
{
tasksAndCancelTokens.Remove(tup);
}
}
Thanks
If you just want to cancel the last task, then that's all you have to do:
CancellationTokenSource cts = null;
async void Button1_Click(...)
{
// Cancel the last task, if any.
if (cts != null)
cts.Cancel();
// Create Cancellation Token for this task.
cts = new CancellationTokenSource();
var token = cts.Token;
// Method to run asynchonously
Func<int> taskAction = () =>
{
// Something time consuming
Thread.Sleep(5000);
token.ThrowIfCancellationRequested();
return 100;
};
try
{
int i = await Task.Run(taskAction);
}
catch (OperationCanceledException)
{
// Don't need to do anything
return;
}
}