await async lambda in ActionBlock - c#

I have a class Receiver with an ActionBlock:
public class Receiver<T> : IReceiver<T>
{
private ActionBlock<T> _receiver;
public Task<bool> Send(T item)
{
if(_receiver!=null)
return _receiver.SendAsync(item);
//Do some other stuff her
}
public void Register (Func<T, Task> receiver)
{
_receiver = new ActionBlock<T> (receiver);
}
//...
}
The Register-Action for the ActionBlock is a async-Method with a await-Statement:
private static async Task Writer(int num)
{
Console.WriteLine("start " + num);
await Task.Delay(500);
Console.WriteLine("end " + num);
}
Now what i want to do is to wait synchronously (if a condition is set) until the action method is finished to get an exclusive behavior:
var receiver = new Receiver<int>();
receiver.Register((Func<int, Task) Writer);
receiver.Send(5).Wait(); //does not wait the action-await here!
The Problem is when the "await Task.Delay(500);" statement is executed, the "receiver.Post(5).Wait();" does not wait anymore.
I have tried several variants (TaskCompletionSource, ContinueWith, ...) but it does not work.
Has anyone an idea how to solve the problem?

ActionBlock by default will enforce exclusive behavior (only one item is processed at a time). If you mean something else by "exclusive behavior", you can use TaskCompletionSource to notify your sender when the action is complete:
... use ActionBlock<Tuple<int, TaskCompletionSource<object>>> and Receiver<Tuple<int, TaskCompletionSource<object>>>
var receiver = new Receiver<Tuple<int, TaskCompletionSource<object>>>();
receiver.Register((Func<Tuple<int, TaskCompletionSource<object>>, Task) Writer);
var tcs = new TaskCompletionSource<object>();
receiver.Send(Tuple.Create(5, tcs));
tcs.Task.Wait(); // if you must
private static async Task Writer(int num, TaskCompletionSource<object> tcs)
{
Console.WriteLine("start " + num);
await Task.Delay(500);
Console.WriteLine("end " + num);
tcs.SetResult(null);
}
Alternatively, you could use AsyncLock (included in my AsyncEx library):
private static AsyncLock mutex = new AsyncLock();
private static async Task Writer(int num)
{
using (await mutex.LockAsync())
{
Console.WriteLine("start " + num);
await Task.Delay(500);
Console.WriteLine("end " + num);
}
}

Related

Cancel Token from infinite Parallel.Foreach loop from an event

I have write some code, where i am using Parallel.Foreach for few items to work parallel with infinite loop i.e working fine after every 60 sec.
But here my message can be change by the user at any time and i need to re-process with new message.
For this, i need to cancel the infinite Parallel.Foreach loop to reprocess the updated message.
when i am trying to reprocess the main method its working fine for new message, but its running twice because the previous scheduled tasks is not canceled. I am assuming i need to cancel process from Parrallel.Foreach loop and re-run again for updated message with new schedule.
So can anyone help me to cancel the queued task that is already scheduled for next 60 second.
static void Main(string[] args)
{
List<RealTimeMessage> messages = GetRealTimeMessage();
Parallel.ForEach(messages, (message) =>
{
processMessage(message);
});
Console.ReadLine();
}
private static async void processMessage(RealTimeMessage message)
{
try
{
while (true)
{
await Task.Delay(TimeSpan.FromSeconds(60));
await Task.Run(() => ProceesRequest(message));
}
}
catch (Exception)
{
Console.WriteLine("Critical error");
}
}
private static List<RealTimeMessage> GetRealTimeMessage()
{
List<RealTimeMessage> realTimeMessages = new List<RealTimeMessage>();
realTimeMessages.Add(new RealTimeMessage { MessageText = "Message 4", IntervalTime = "1", MessageType = "AIDX", TimeOfDay = "" });
realTimeMessages.Add(new RealTimeMessage { MessageText = "Message 5", IntervalTime = "2", MessageType = "AMSX", TimeOfDay = "" });
return realTimeMessages;
}
private static void ProceesRequest(RealTimeMessage message)
{
// do domething
}
This is a misuse of Parallel.ForEach, use Task.WhenAll instead
Don't start a Task in ProcessMessage (this could be intentional, however it looks like a mistake).
Use a CancellationToken to cancel a task
Don't use async void unless it's for an event
Use standard casing for method names
Don't use while(true) use while (!token.IsCancellationRequested)
When all things are considered, it would look something like this
static async Task Main(string[] args)
{
var ts = new CancellationTokenSource();
var messages = GetRealTimeMessage();
var tasks = messages.Select(x => ProcessMessage(x, ts.Token));
Console.WriteLine("Press any key to cancel tasks")
Console.ReadKey();
ts.Cancel();
await Task.WhenAll(tasks);
Console.WriteLine("All finished");
Console.ReadKey();
}
private static async Task ProcessMessage( RealTimeMessage message, CancellationToken token )
{
try
{
while (!token.IsCancellationRequested)
{
await Task.Delay(TimeSpan.FromSeconds(60), token);
ProcessRequest(message);
}
}
catch (OperationCanceledException)
{
Console.WriteLine("Operation Cancelled");
}
catch (Exception ex)
{
Console.WriteLine("Critical error: " + ex.Message);
}
}
To cancel your tasks, just call ts.Cancel().

How to restart a async method? Cancel previous run, await it and then start it

I have a method RestartAsync which starts a method DoSomethingAsync. When RestartAsync is called again it should cancel DoSomethingAsyncand await until it is finished (DoSomethingAsync can NOT be cancelled synchronously and it should NOT be called when a previous task is still in progress).
My first approach looked like this:
public async Task RestartTest()
{
Task[] allTasks = { RestartAsync(), RestartAsync(), RestartAsync() } ;
await Task.WhenAll(allTasks);
}
private async Task RestartAsync()
{
_cts.Cancel();
_cts = new CancellationTokenSource();
await _somethingIsRunningTask;
_somethingIsRunningTask = DoSomethingAsync(_cts.Token);
await _somethingIsRunningTask;
}
private static int _numberOfStarts;
private async Task DoSomethingAsync(CancellationToken cancellationToken)
{
_numberOfStarts++;
int numberOfStarts = _numberOfStarts;
try
{
Console.WriteLine(numberOfStarts + " Start to do something...");
await Task.Delay(TimeSpan.FromSeconds(1)); // This operation can not be cancelled.
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
Console.WriteLine(numberOfStarts + " Finished to do something...");
}
catch (OperationCanceledException)
{
Console.WriteLine(numberOfStarts + " Cancelled to do something...");
}
}
The actual output when calling RestartAsync three times looks like this (Note that the second run is cancelling and awaiting the first, but at the same time the third run is also awaiting the first instead of cancelling and awaiting the second one):
1 Start to do something...
1 Cancelled to do something...
2 Start to do something...
3 Start to do something...
2 Finished to do something...
3 Finished to do something...
But what I want to achieve is this output:
1 Start to do something...
1 Cancelled to do something...
2 Start to do something...
2 Cancelled to do something...
3 Start to do something...
3 Finished to do something...
My current solution is the following:
private async Task RestartAsync()
{
if (_isRestarting)
{
return;
}
_cts.Cancel();
_cts = new CancellationTokenSource();
_isRestarting = true;
await _somethingIsRunningTask;
_isRestarting = false;
_somethingIsRunningTask = DoSomethingAsync(_cts.Token);
await _somethingIsRunningTask;
}
Then I get this output:
1 Start to do something...
1 Cancelled to do something...
2 Start to do something...
2 Finished to do something...
Now at least DoSomethingAsync is not started while it is still in progress (Note that third run is ignored, which does not really matter, because it should cancel the second run otherwise).
But this solution doesn't feel good and I have to repeat this ugly pattern wherever I want this kind of behavior. Is there any good pattern or framework for this kind of restart mechanic?
I think the problem is inside RestartAsync method. Beware that an async method will immediately return a task if it's going to await something, so second RestartAsync actually return before it swap its task then third RestartAsync comes in and awaiting the task first RestartAsync.
Also if RestartAsync is going to be executed by multiple thread, you may want to wrap _cts and _somethingIsRunningTask into one and swap values with Interlocked.Exchange method to prevent race condition.
Here is my example code, not fully tested:
public class Program
{
static async Task Main(string[] args)
{
RestartTaskDemo restartTaskDemo = new RestartTaskDemo();
Task[] tasks = { restartTaskDemo.RestartAsync( 1000 ), restartTaskDemo.RestartAsync( 1000 ), restartTaskDemo.RestartAsync( 1000 ) };
await Task.WhenAll( tasks );
Console.ReadLine();
}
}
public class RestartTaskDemo
{
private int Counter = 0;
private TaskEntry PreviousTask = new TaskEntry( Task.CompletedTask, new CancellationTokenSource() );
public async Task RestartAsync( int delay )
{
TaskCompletionSource<bool> taskCompletionSource = new TaskCompletionSource<bool>();
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
TaskEntry previousTaskEntry = Interlocked.Exchange( ref PreviousTask, new TaskEntry( taskCompletionSource.Task, cancellationTokenSource ) );
previousTaskEntry.CancellationTokenSource.Cancel();
await previousTaskEntry.Task.ContinueWith( Continue );
async Task Continue( Task previousTask )
{
try
{
await DoworkAsync( delay, cancellationTokenSource.Token );
taskCompletionSource.TrySetResult( true );
}
catch( TaskCanceledException )
{
taskCompletionSource.TrySetCanceled();
}
}
}
private async Task DoworkAsync( int delay, CancellationToken cancellationToken )
{
int count = Interlocked.Increment( ref Counter );
Console.WriteLine( $"Task {count} started." );
try
{
await Task.Delay( delay, cancellationToken );
Console.WriteLine( $"Task {count} finished." );
}
catch( TaskCanceledException )
{
Console.WriteLine( $"Task {count} cancelled." );
throw;
}
}
private class TaskEntry
{
public Task Task { get; }
public CancellationTokenSource CancellationTokenSource { get; }
public TaskEntry( Task task, CancellationTokenSource cancellationTokenSource )
{
Task = task;
CancellationTokenSource = cancellationTokenSource;
}
}
}
This is a concurrency problem. So, you'll need a solution for concurrency problems: a semaphore.
In the generic case, you should account also for when the method being runs throws an OperationCanceledException:
private async Task DoSomethingAsync(CancellationToken cancellationToken)
{
_numberOfStarts++;
int numberOfStarts = _numberOfStarts;
try
{
Console.WriteLine(numberOfStarts + " Start to do something...");
await Task.Delay(TimeSpan.FromSeconds(1)); // This operation can not be cancelled.
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
Console.WriteLine(numberOfStarts + " Finished to do something...");
}
catch (OperationCanceledException)
{
Console.WriteLine(numberOfStarts + " Cancelled to do something...");
throw;
}
}
Try this:
private SemaphoreSlim semaphore = new SemaphoreSlim(1);
private (CancellationTokenSource cts, Task task)? state;
private async Task RestartAsync()
{
Task task = null;
await this.semaphore.WaitAsync();
try
{
if (this.state.HasValue)
{
this.state.Value.cts.Cancel();
this.state.Value.cts.Dispose();
try
{
await this.state.Value.task;
}
catch (OperationCanceledException)
{
}
this.state = null;
}
var cts = new CancellationTokenSource();
task = DoSomethingAsync(cts.Token);
this.state = (cts, task);
}
finally
{
this.semaphore.Release();
}
try
{
await task;
}
catch (OperationCanceledException)
{
}
}

How to get handle of an awaitable Task?

I am a newbie in Tasks and still learning this topic so be gentle with me (I think I have some fundamental mess-ups with my below code...)
Please look at the below exercise which will help me describe my question:
I have a simple "MyService" class which has a "Do_CPU_Intensive_Job" method called by the "Run" method. My purpose is to be able to run several instances of the "Do_CPU_Intensive_Job" method (which itself run on a different thread than the UI as its CPU bound), sometimes synchronously and sometimes asynchronously.
In other words, assuming I have 2 instances of MyService, sometimes I want these 2 methods running together and sometimes not.
class MyService
{
private bool async;
private string name;
private CancellationTokenSource tokenSource;
private CancellationToken token;
private bool isRunning = false;
private Task myTask = null;
public MyService(string name, bool async)
{
this.name = name;
this.async = async;
}
public string Name { get { return name; } }
public bool IsRunning { get { return isRunning; } }
public async Task Run ()
{
isRunning = true;
tokenSource = new CancellationTokenSource();
token = tokenSource.Token;
if (async)
myTask = Do_CPU_Intensive_Job();
else
await Do_CPU_Intensive_Job(); // I cannot do myTask = await Do_CPU_Intensive_Job(); so how can the "Stop" method wait for it??
}
public async Task Stop ()
{
tokenSource.Cancel();
if (myTask != null)
await myTask;
isRunning = false;
}
private async Task Do_CPU_Intensive_Job ()
{
Console.WriteLine("doing some heavy job for Task " + name);
int i = 0;
while (!token.IsCancellationRequested)
{
Console.WriteLine("Task: " + name + " - " + i);
await Task.Delay(1000);
i++;
}
Console.WriteLine("Task " + name + " not yet completed! I need to do some cleanups");
await Task.Delay(2000); //simulating cleanups
Console.WriteLine("Task " + name + " - CPU intensive and cleanups done!");
}
}
So, I have the below GUI which which works well but only if the 2 instances are running asynchronously. "works well" means that when stopping the tasks, it stops nicely by running entire "Do_CPU_Intensive_Job" method. hence the last message will be from the GUI ("Both tasks are completed...now doing some other stuff"). So far so good.
public partial class Form1 : Form
{
List<MyService> list = null;
MyService ms1 = null;
MyService ms2 = null;
public Form1()
{
InitializeComponent();
list = new List<MyService>();
ms1 = new MyService("task 1", true);
ms2 = new MyService("task 2", true);
list.Add(ms1);
list.Add(ms2);
}
private async void button1_Click(object sender, EventArgs e)
{
foreach (MyService item in list)
await item.Run();
}
private async void button2_Click(object sender, EventArgs e)
{
foreach (MyService item in list)
{
if (item.IsRunning)
{
await item.Stop();
Console.WriteLine("Done stopping Task: " + item.Name);
}
}
//now ready to do some other stuff
Console.WriteLine("Both tasks are completed...now doing some other stuff");
}
}
Problem starts when the 2 instances are not running simultaneously. In that case, I get the "Both tasks are completed...now doing some other stuff" from the GUI before "Do_CPU_Intensive_Job" is really completed...
ms1 = new MyService("task 1", false);
ms2 = new MyService("task 2", false);
this is not happening when both tasks are running together because I have the handle (myTask) when running asynchronously which I dont when running synchronously.
await Do_CPU_Intensive_Job(); // I cannot do myTask = await Do_CPU_Intensive_Job(); so how can the "Stop" method wait for it??
Thanks, all
I spent some time hammering out the code to a point that I think it is doing what is expected.
The first problem I found is that you can't just pass the cancellation token into your method, you need to relate it to the task(s) that are to be cancelled. Unfortunately I could not find a way to do this directly on an async method but have a look at the MyService class here for how I was able to do this.
class MyService
{
private bool async;
private string name;
private CancellationTokenSource tokenSource;
private bool isRunning = false;
private Task myTask = null;
public MyService(string name, bool async)
{
this.name = name;
this.async = async;
}
public string Name { get { return name; } }
public bool IsRunning { get { return isRunning; } }
public async Task Run()
{
isRunning = true;
tokenSource = new CancellationTokenSource();
myTask = Task.Run(() => Do_CPU_Intensive_Job(tokenSource.Token), tokenSource.Token);
if (!async)
await myTask;
}
public async Task Stop()
{
tokenSource.Cancel();
if (myTask != null)
await myTask;
isRunning = false;
}
private void Do_CPU_Intensive_Job(CancellationToken token)
{
Console.WriteLine("doing some heavy job for Task " + name);
int i = 0;
while (!token.IsCancellationRequested)
{
Console.WriteLine("Task: " + name + " - " + i);
Thread.Sleep(1000);
i++;
}
Console.WriteLine("Task " + name + " not yet completed! I need to do some cleanups");
Thread.Sleep(1000);
Console.WriteLine("Task " + name + " - CPU intensive and cleanups done!");
}
}
The Run method is now using Task.Run to call Do_CPU_Intensive_Job and if you note I am passing the token to both the work method and to the Task.Run call. The latter is what links the token to that Task/Thread and the former is what allows us to watch for the cancellation request.
The final piece is how we call Run on the service instances, by calling await on a Task or async method the thread is being released but the remainder of the code in the method is extracted and will not be run until the awaited task completes.
I was just using a unit test in order to work on the code rather than a button but the premise should be the same, but here is how I was able to run the tasks in synchronous mode and still be able to call stop on them.
var service1 = new MyService("task 1", false);
var service2 = new MyService("task 2", false);
service1.Run(); //Execution immediately moves to next line
service2.Run(); // Same here
await service1.Stop(); //Execution will halt here until task one has fully stopped so task 2 actually continues running
await service2.Stop();

C# TAP async Multiple enqueue Delay then dequeue all

I have a system where a method is called with an object and the database is written with a different object with a list of the first .
Currently :
private async Task SaveAMessage(Messsage message)
{
var messages = new List<Message>();
messages.Add(message);
var envelope = new Envelope();
envelope.messages = messages;
await _db.Save(envelope);
}
But I can only run _db.Save every 1 second.
What is the TAP way of saying: Add this item to the list and then save them all together after 1 second. Below I have some fake code that expresses what I wish I could write.
Javascript-y Pseudo Code:
private List<Message> messages = new List<Message>();
private int? valueCheckTimer;
private async Task SaveAMessage(Messsage message)
{
messages.Add(message);
if (valueCheckTimer) {
return;
}
valueCheckTimer = setTimeout(function () {
var envelope = new Envelope();
envelope.messages = messages;
await _db.Save(envelope);
messages.Clear();
},1000);
}
How do I write C# code that acts the way the pseudo-code works?
You can actually do this with just one small really simple change. Add a WhenAll to SaveAMessage to await both Save as well as a call to Task.Delay:
private async Task SaveAMessage(Messsage message)
{
var messages = new List<Message>();
messages.Add(message);
var envelope = new Envelope();
envelope.messages = messages;
await Task.WhenAll(_db.Save(envelope), Task.Delay(1000));
}
Now you can just loop through all of your calls to SaveAMessage, awaiting them all, and you can be sure that it waits until the previous save is done and that at least a second has passed before continuing.
If you sometimes don't need to wait a full second when using SaveAMessage elsewhere, then simply pull this change out and have whatever code you're using to save all of your messages await the Task.Dealy call.
Try this piece of c# code (run as ConsoleApp):
namespace ConsoleApplication1
{
public class Program
{
private static async Task<string> SaveAMessage(string message)
{
var messages = new List<string>();
messages.Add(message);
return await save(messages);
}
private static Task<string> save(List<string> msg)
{
Task<string> task = Task.Factory.StartNew<string>(() =>
{
Console.WriteLine("Message " + msg[0] + " received...");
Console.WriteLine("Message " + msg[0] + " running...");
Thread.Sleep(3000);
return "Message " + msg[0] + " finally return.";
});
return task;
}
public static void Main(string[] args)
{
Task<string> first = SaveAMessage("Msg1");
first.ContinueWith(x => Console.WriteLine("Print " + x.Result));
Task<string> second = SaveAMessage("Msg2");
second.ContinueWith(x => Console.WriteLine("Print " + x.Result));
Task<string> third = SaveAMessage("Msg3");
third.ContinueWith(x => Console.WriteLine("Print " + x.Result));
Task<string> fourth = SaveAMessage("Msg4");
fourth.ContinueWith(x => Console.WriteLine("Print " + x.Result));
Task<string> fifth = SaveAMessage("Msg5");
fifth.ContinueWith(x => Console.WriteLine("Print " + x.Result));
Console.ReadKey();
}
}
}

How to cancel a Task in await?

I'm playing with these Windows 8 WinRT tasks, and I'm trying to cancel a task using the method below, and it works to some point. The CancelNotification method DOES get called, which makes you think the task was cancelled, but in the background the task keeps running, then after it's completed, the status of the Task is always completed and never cancelled. Is there a way to completely halt the task when it's cancelled?
private async void TryTask()
{
CancellationTokenSource source = new CancellationTokenSource();
source.Token.Register(CancelNotification);
source.CancelAfter(TimeSpan.FromSeconds(1));
var task = Task<int>.Factory.StartNew(() => slowFunc(1, 2), source.Token);
await task;
if (task.IsCompleted)
{
MessageDialog md = new MessageDialog(task.Result.ToString());
await md.ShowAsync();
}
else
{
MessageDialog md = new MessageDialog("Uncompleted");
await md.ShowAsync();
}
}
private int slowFunc(int a, int b)
{
string someString = string.Empty;
for (int i = 0; i < 200000; i++)
{
someString += "a";
}
return a + b;
}
private void CancelNotification()
{
}
Read up on Cancellation (which was introduced in .NET 4.0 and is largely unchanged since then) and the Task-Based Asynchronous Pattern, which provides guidelines on how to use CancellationToken with async methods.
To summarize, you pass a CancellationToken into each method that supports cancellation, and that method must check it periodically.
private async Task TryTask()
{
CancellationTokenSource source = new CancellationTokenSource();
source.CancelAfter(TimeSpan.FromSeconds(1));
Task<int> task = Task.Run(() => slowFunc(1, 2, source.Token), source.Token);
// (A canceled task will raise an exception when awaited).
await task;
}
private int slowFunc(int a, int b, CancellationToken cancellationToken)
{
string someString = string.Empty;
for (int i = 0; i < 200000; i++)
{
someString += "a";
if (i % 1000 == 0)
cancellationToken.ThrowIfCancellationRequested();
}
return a + b;
}
Or, in order to avoid modifying slowFunc (say you don't have access to the source code for instance):
var source = new CancellationTokenSource(); //original code
source.Token.Register(CancelNotification); //original code
source.CancelAfter(TimeSpan.FromSeconds(1)); //original code
var completionSource = new TaskCompletionSource<object>(); //New code
source.Token.Register(() => completionSource.TrySetCanceled()); //New code
var task = Task<int>.Factory.StartNew(() => slowFunc(1, 2), source.Token); //original code
//original code: await task;
await Task.WhenAny(task, completionSource.Task); //New code
You can also use nice extension methods from https://github.com/StephenCleary/AsyncEx and have it looks as simple as:
await Task.WhenAny(task, source.Token.AsTask());
One case which hasn't been covered is how to handle cancellation inside of an async method. Take for example a simple case where you need to upload some data to a service get it to calculate something and then return some results.
public async Task<Results> ProcessDataAsync(MyData data)
{
var client = await GetClientAsync();
await client.UploadDataAsync(data);
await client.CalculateAsync();
return await client.GetResultsAsync();
}
If you want to support cancellation then the easiest way would be to pass in a token and check if it has been cancelled between each async method call (or using ContinueWith). If they are very long running calls though you could be waiting a while to cancel. I created a little helper method to instead fail as soon as canceled.
public static class TaskExtensions
{
public static async Task<T> WaitOrCancel<T>(this Task<T> task, CancellationToken token)
{
token.ThrowIfCancellationRequested();
await Task.WhenAny(task, token.WhenCanceled());
token.ThrowIfCancellationRequested();
return await task;
}
public static Task WhenCanceled(this CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<bool>();
cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).SetResult(true), tcs);
return tcs.Task;
}
}
So to use it then just add .WaitOrCancel(token) to any async call:
public async Task<Results> ProcessDataAsync(MyData data, CancellationToken token)
{
Client client;
try
{
client = await GetClientAsync().WaitOrCancel(token);
await client.UploadDataAsync(data).WaitOrCancel(token);
await client.CalculateAsync().WaitOrCancel(token);
return await client.GetResultsAsync().WaitOrCancel(token);
}
catch (OperationCanceledException)
{
if (client != null)
await client.CancelAsync();
throw;
}
}
Note that this will not stop the Task you were waiting for and it will continue running. You'll need to use a different mechanism to stop it, such as the CancelAsync call in the example, or better yet pass in the same CancellationToken to the Task so that it can handle the cancellation eventually. Trying to abort the thread isn't recommended.
I just want to add to the already accepted answer. I was stuck on this, but I was going a different route on handling the complete event. Rather than running await, I add a completed handler to the task.
Comments.AsAsyncAction().Completed += new AsyncActionCompletedHandler(CommentLoadComplete);
Where the event handler looks like this
private void CommentLoadComplete(IAsyncAction sender, AsyncStatus status )
{
if (status == AsyncStatus.Canceled)
{
return;
}
CommentsItemsControl.ItemsSource = Comments.Result;
CommentScrollViewer.ScrollToVerticalOffset(0);
CommentScrollViewer.Visibility = Visibility.Visible;
CommentProgressRing.Visibility = Visibility.Collapsed;
}
With this route, all the handling is already done for you, when the task is cancelled it just triggers the event handler and you can see if it was cancelled there.

Categories