A Task does not cancels with a CancellationTokenSource - c#

I have a code. The goal of this is to cancel a task with a CancellationToken, I know that it possible to do with return; in loop, but I want to do it with CancellationToken. I tried to do it, but it does not work and I have no idea why.
The task
break a task loop on dropNumber
static CancellationTokenSource cancellationTokenSource = null;
static async Task Main(string[] args)
{
cancellationTokenSource = new CancellationTokenSource();
try
{
Task.Run(() => CountLoopAsync(cancellationTokenSource.Token, 4),cancellationTokenSource.Token);
}
catch(OperationCanceledException ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.BackgroundColor = ConsoleColor.White;
Console.WriteLine("Task is cancelled!");
Console.ResetColor();
}
finally
{
cancellationTokenSource.Dispose();
}
}
private static void CountLoopAsync(CancellationToken token, int dropNumber)
{
for(int i = 0; i < 10; i++)
{
Console.WriteLine(i);
if (dropNumber == i)
{
cancellationTokenSource.Cancel();
}
}
}
}

Your Task.Run it's done with await so you don't go to Cancel sentence until the task has finished. Use Task.Run without await to allow continue running and execute the Cancel
UPDATE
I think that your example if not so good because you are trying to execute all code in a sequencial way when the use of task is usually to run code in background, in an asynchronous form. Also, I think that stop with a predefined value is a non sense: in that case, change the final step in your "for" instead of the use of a token.
You don't Cancel in the task code. If you know in that code when to cancel, you simply return. The purpose of the token is allow to cancel externally to task code. And doing that, you can't control when the task finish because it depends of something external. Maybe, for example, when an user click "Cancel" button.
Usually, your counter code try to calculate all. But, been a long time operation, you give to the user the oportunity to cancel in any moment.
Encapsulate your code in a class:
public class YourClass : IDisposable
{
private CancellationTokenSource _cancellationTokenSource = null;
private Task _task = null;
public void Wait(int milliSeconds)
{
this._task.Wait(milliSeconds, this._cancellationTokenSource.Token);
}
public void Dispose()
{
this._cancellationTokenSource?.Dispose();
this._task?.Dispose();
}
public async Task RunLongOperationInBackground()
{
this._cancellationTokenSource = new CancellationTokenSource();
this._task = Task.Run(
() => CountLoopAsync(this._cancellationTokenSource.Token),
this._cancellationTokenSource.Token);
await this._task;
}
public void Abort()
{
// Cancel the operation
this._cancellationTokenSource?.Cancel();
}
private static void CountLoopAsync(CancellationToken token)
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine(i);
// Uncomment to simulate task takes some time to finish
//Thread.Sleep(3000);
// You don't know when the action will be cancelled. If you know that, you don't
// need the cancellation: you can do the for until your well known end
if (token.IsCancellationRequested)
{
break;
}
}
}
}
This class allow you run an operation (RunLongOperationInBackground) and also cancel in any moment (Abort). So, you run your task and, in any moment, you can cancel the task. If you look the CountLoopAsync code, it tries to execute all but, it checks sometimes (in each iteration in this case) the token, and if someone has request to cancel, you exit the for. But you can do whatever you want. For example, you may run always up to next hundred so, even if token has been cancelled, you may continue up to next hundred. Or if the cancellation has been requested near to the end of the operation, you may decide continue. The token only tell you that outside wants terminate.
Create a Form (instead a console) with 2 buttons, for a more realistic example:
public partial class Form1 : Form
{
private readonly YourClass _yourClass;
public Form1()
{
this.InitializeComponent();
this._yourClass = new YourClass();
}
private async void OnStartButtonClick(object sender, EventArgs e)
{
await this._yourClass.RunLongOperationInBackground();
}
private void OnCancelButtonClick(object sender, EventArgs e)
{
this._yourClass.Abort();
}
private void OnForm_FormClosed(object sender, FormClosedEventArgs e)
{
if (this._yourClass != null)
{
// Wait, for example 30 seconds before end the appication
this._yourClass.Wait(30000);
this._yourClass.Dispose();
}
}
}
You create your class in the constructor. The Start button run your long time operation (you may want use a Delay in each for iteration to be able to cancel before terminate). In any time you can click de Abort button to cancel the operation. And in that moment, in your "for" the token tell you that has been cancelled and you exit the for.

I think that the problem is here:
cancellationTokenSource.Dispose();
It seems that the cancellationTokenSource is disposed prematurely. You are not supposed to dispose it before all associated work has completed. In your case you must probably wait for the completion of the Task.Run before calling Dispose.
Task.Run(() => CountLoopAsync(cancellationTokenSource.Token, 4),
cancellationTokenSource.Token).Wait();

Related

Dynamically swapping out or toggling visibility of controls

I have a TreeView in a form, that is dock-filled to a groupbox. The problem to solve is, that there is an operation that is run on a Task, which loads data from a server application. When this is running, there should be a progress indicator displayed in the location of the TreeView. That is, it should be shown instead of the TreeView, and take its place fully. The following is what the code for this looks like:
private async void preload_data(object sender, System.EventArgs args)
{
try
{
// the treeView should be disabled/invisible at this point
// make the CircularProgressBar enabled/visible
// get the information from the server
await Configuration.network.fetch_stuff();
}
catch (Exception ex)
{
// something bad happened
}
finally
{
// whatever happened, the treeView should be back
}
}
The CircularProgressBar (a third-party control) should appear as in the code above, and should replace the TreeView. It should fill the exact same space as the TreeView would, which is dock-filled. Below is a screenshot of this:
This form and all its controls have been designed in the designer, and I don't want to do it there, I want to do it programmatically. What is the best way to go about this? I have looked at examples of Controls.Remove() and Controls.Add(), but it's not clear if that fits this purpose.
It is quite common to change the visual output while actions are running, like you do. Think of disabling buttons, to discourage operators to press the button again, or show something visually, to inform operators about the progress.
For simplicity, without the try-catch
private async Task PreloadDataAsync()
{
this.ShowFetchingData(true);
// start fetching data, do not await:
var taskFetchData = Configuration.network.fetch_stuff();
// while taskFetchData not completed, await some time
TimeSpan updateTime = TimeSpan.FromSeconds(0.250);
int progressCounter = 0;
while (!taskFetchData.IsCompleted)
{
this.ShowProgress(progressCounter);
var taskWait = Task.Delay(updateTime);
await Task.WhenAny(new Task[] {taskFetchData, taskWait};
// either taskFetchData.IsCompleted, or Delay time waited
++progressCounter;
}
this.ShowFetchingData(false);
}
private void ShowFetchindData(bool show)
{
// disable/enable certain buttons, menu items, show progressbar?
this.ButtonFetchData.Enabled = !show;
this.MenuFetchData.Enabled = !show;
this.ProgressBarFetchData.Visible = show;
}
private bool IsFetchingData => this.ProgressBarFetchData.Visible;
private void ShowProgress(int progress)
{
this.ProgressBarFetchData.Position = progress;
}
For simplicity, I've left out checks for the position in the progress bar, but you get the gist.
Usage:
private async void OnButtonFetchData(object sender, EventArgs e)
{
await this.PreloadDataAsync();
}
Room for improvement
The problem with this is that there is no timeout at all: if FetchStuff does not complete, you are in an endless wait. The method that microsoft proposes is the use of a CancellationToken. Almost every async method has an overload with a CancellationToken. Consider creating one yourself:
// existing method:
private async Task<MyData> fetch_Stuff()
{
await this.fetch_stuff(CancellationToken.None);
}
// added method with CancellationToken
private async Task<MyData> fetch_Stuff(CancellationToken token)
{
// Call async function overloads with the token,
// Regularly check if cancellation requested
while (!token.IsCancellationRequested)
{
... // fetch some more data, without waiting too long
}
}
Instead of IsCancellationRequested, consider to throw an exception: ThrowIfCancellationRequested.
Usage:
private async Task PreloadDataAsync()
{
// preloading should be finished within 30 seconds
// let the cancellationTokenSource request cancel after 30 seconds
TimeSpan maxPreloadTime = TimeSpan.FromSeconds(30);
using (var cancellationTokenSource = new CancellationTokenSource(maxPreloadTime))
{
await PreloadDataAsync(cancellationTokenSource.Token);
}
}
The overload with CancellationToken:
private async Task PreloadDataAsync(CancellationToken token)
{
this.ShowFetchingData(true);
// execute code similar to above, use overloads that accept token:
try
{
var taskFetchData = Configuration.network.fetch_stuff(token);
TimeSpan updateTime = TimeSpan.FromSeconds(0.250);
int progressCounter = 0;
while (!taskFetchData.IsCompleted)
{
token.ThrowIfCancellationRequested();
this.ShowProgress(progressCounter);
var taskWait = Task.Delay(updateTime, token);
await Task.WhenAny(new Task[] {taskFetchData, taskWait};
// either taskFetchData.IsCompleted, or Delay time waited
++progressCounter;
}
}
catch (TaskCancelledException exc)
{
this.ReportPreloadTimeout();
}
finally
{
this.ShowFetchingData(false);
}
}
Or if you want a button that cancels the task:
private CancellationTokenSource cancellationTokenSource = null;
private book IsPreloading => this.CancellationTokenSource != null;
private async Task StartStopPreload()
{
if (!this.IsPreloading)
StartPreload();
else
CancelPreload();
}
private async Task StartPreload()
{
// preload not started yet; start it without timeout;
try
{
this.cancellationTokenSource = new CancellationTokenSource();
await PreloadDataAsync(this.cancellationTokenSource.Token);
}
catch (TaskCancelledException exc)
{
this.ReportPreloadCancelled();
}
finally
{
this.cancellationTokenSource.Dispose();
this.cancellationTokenSource = null;
}
}
}
The method where operators can stop preloading:
private async void StopPreload()
{
this.cancellationTokenSource.Cancel();
// the method that created this source will Dispose it and assign null
}
All you have to do is create buttons / menu items to start / stop preloading
Solved using Visible properties of controls.

How to make a controlled infinite loop async in c#?

I found this
Run async method regularly with specified interval
which does half of what I want, but at the same time I want to be able to stop the loop whenever I want and then resume it as well. However while it's stopped, I don't want the infinite loop to keep running where the body gets skipped through a flag.
Basically I don't want this
while (true) {
if (!paused) {
// run work
}
// task delay
}
because then the while loop still runs.
How can I set it so that while its paused, nothing executes?
How can I set it so that while its paused, nothing executes?
That's hard to answer: if you define "pause" as: the object state remains valid while the loop doesn't use any resources then you'll have to stop and restart it (the loop).
All other timers, including Thread.Sleep, Task.Delays etc. will put your thread in idle/suspended mode.
If that's not sufficient for your needs, you'll need to actually stop the "infinite" loop.
It will free up thread related resources as well.
More info about sleep:
Thread.Sleep
More about sleep
You could use System.Threading.Timer and dispose of it while it is not in use and re-create it when you are ready to "resume". These timers are light weight so creating and destroying them on demand is not a problem.
private System.Threading.Timer _timer;
public void StartResumeTimer()
{
if(_timer == null)
_timer = new System.Threading.Timer(async (e) => await DoWorkAsync(e), null, 0, 5000);
}
public void StopPauseTimer()
{
_timer?.Dispose();
_timer = null;
}
public async Task DoWorkAsync(object state)
{
await Task.Delay(500); // do some work here, Task.Delay is just something to make the code compile
}
If you are really adverse to timers and want it to look like a while loop, then you can use TaskCompletionSource<T>:
private TaskCompletionSource<bool> _paused = null;
public async Task DoWork()
{
while (true)
{
if (_paused != null)
{
await _paused.Task;
_paused = null;
}
//run work
await Task.Delay(100);
}
}
public void Pause()
{
_paused = _paused ?? new TaskCompletionSource<bool>();
}
public void UnPause()
{
_paused?.SetResult(true);
}

Wants to make KeyBoard Action with an Option to Stop

I want to create a program that makes keyboard event for every 10 seconds.
but once my code starts to perform,
it can't stop until it's finished.
Is there any way to stop to close the program??
Can I make it detect my keyboard action by two seconds, like detect hitting ESC to stop the program?
My code is at below.
private void StartAction()
{
int minutes = 2;
Process pr = Process.Start("notepad.exe");
for (int a = 0; a < minutes; a++)
{
for (int b = 0; b < 6; b++)
{
Thread.Sleep(2000);
Thread.Sleep(2000);
Thread.Sleep(2000);
Thread.Sleep(2000);
Thread.Sleep(2000);
SendKeys.Send("KeyBoard Action");
}
}
}
Sometimes its easier to just use a timer.
using System.Threading;
...
private static Timer _myTimer;
private static volatile bool _isCancled;
private static void Main(string[] args)
{
_myTimer = new Timer(Callback, null, 2000, 2000);
Console.ReadLine();
}
private static void Callback(object state)
{
if (_isCancled)
{
_myTimer.Change(0, 0);
return;
}
try
{
//SendKeys.Send("KeyBoard Action");
Console.WriteLine("blah");
}
catch (Exception ex)
{
//Handle Exception
}
}
or another way
Task.Run(async () =>
{
while (!_isCancled)
{
await Task.Delay(2000);
//SendKeys.Send("KeyBoard Action");
Console.WriteLine("blah");
}
});
Just for completeness, the optimal way to cancel a task is using a Cancellation Token. You can read more about them here
CancellationToken Structure
Propagates notification that operations should be canceled.
Add a condition inside the for loop, so that it will exit when requested.
if (condition)
{
break;
}
Edit: Because of the Thread.Sleep everywhere, the program won't actually be able to have a conditional change, example, if you're condition was when the user pressed f1, the condition would never become true because the Thread.Sleep would block the thread, therefore the users input would never be recognized. Try not to use so many Thread.Sleep, if any at all. If it is absolutely necessary, I advise you to use an async method, or use Timers.

How can I create call an async method inside another async method without it stalling

I need to call multiple async methods and inside of them, call another async method aswell.
Let me demonstrate
private async void button1_Click(object sender, EventArgs e)
{
for(int i = 0; i< 100; i++)
{
await Method1();
}
}
public async Task Method1()
{
await Task.Delay(3*1000);
await Method2();
}
public async Task Method2()
{
await Task.Delay(10*1000);
}
My problem is, the for statement only activates the iterations after the wait on Method2 starts and what I want is to create the 100 Task all at once. Everything else will be done asynchronously.
I think you are confused as to what "await" means. "await" means "start processing this thing asynchronously, and while that is ticking away, go back to the windows message loop and keep on processing messages so that the UI keeps on re-drawing. When the asynchronous task is done, pick up the code where I awaited".
So when you await in a loop like that, you are saying:
Start the first asynchronous job...
and while it is running, keep processing the message loop.
When the first asynchronous job is done...
pick up in the loop where we awaited. We go around the loop and...
Start the second asynchronous job...
If that's not what you want then don't await the task. If you want to start a hundred tasks that run concurrently then start a hundred tasks and don't await any of them. Why is there any await in the click handler?
Rather than await-ing each tasks as you create it in the for loop, you just want to start all of the tasks. You don't want to delay the scheduling of the next task until the previous finished, so just don't await:
private void button1_Click(object sender, EventArgs e)
{
for(int i = 0; i< 100; i++)
{
var task = Method1();
}
}
Done.
If it's important that you do something after all of the tasks finish, while still doing all of them in parallel, then you can rely on Task.WhenAll to generate a task that will be completed when all of the other tasks are done:
private async void button1_Click(object sender, EventArgs e)
{
var tasks = new List<Task>();
for(int i = 0; i< 100; i++)
{
tasks.Add(Method1());
}
await Task.WhenAll(tasks);
textbox1.Text = "Done!"; //or whatever you want to do when they're all done
}

Wait for a while without blocking main thread

I wish my method to wait about 500 ms and then check if some flag has changed. How to complete this without blocking the rest of my application?
You can use await Task.Delay(500); without blocking the thread like Sleep does, and with a lot less code than a Timer.
Thread.Sleep(500) will force the current thread to wait 500ms. It works, but it's not what you want if your entire application is running on one thread.
In that case, you'll want to use a Timer, like so:
using System.Timers;
void Main()
{
Timer t = new Timer();
t.Interval = 500; // In milliseconds
t.AutoReset = false; // Stops it from repeating
t.Elapsed += new ElapsedEventHandler(TimerElapsed);
t.Start();
}
void TimerElapsed(object sender, ElapsedEventArgs e)
{
Console.WriteLine("Hello, world!");
}
You can set AutoReset to true (or not set it at all) if you want the timer to repeat itself.
I don't really understand the question.
If you want to block before checking, use Thread.Sleep(500);
If you want to check asynchronously every x seconds, you can use a Timer to execute a handler every x milliseconds.
This will not block your current thread.
It the method in question is executing on a different thread than the rest of your application, then do the following:
Thread.Sleep(500);
System.Threading.Thread.Sleep(500);
Update
This won't block the rest of your application, just the thread that is running your method.
Using a timer should do the trick
if you need to use a thread then here is an example
void Main()
{
System.Threading.Thread check= new System.Threading.Thread(CheckMethod);
check.Start();
}
private void CheckMethod()
{
//Code
Thread.Sleep(500);
}
Asynchron Task:
var task = new Task (() => function_test()); task.Start();
public void function_test() { `Wait for 5000 miliseconds` Task.Delay(5000);` }
I've recently been struggling with the same issue where I needed an action to be run on schedule without blocking the UI.
Here's my solution:
private void Button_Click(object sender, RoutedEventArgs e)
{
RunOnSchedule(interval, cancellationToken);
}
private void RunOnSchedule(int interval, CancellationToken cancellationToken)
{
// Start the task you want to run on schedule
TaskToRunOnSchedule(args);
Task.Run(async () =>
{
// This loop checks if the task was requested to be cancelled every 1000 ms
for (int x = 0; x < interval; x+=1000)
{
if (cancellationToken.IsCancellationRequested)
{
break;
}
await Task.Delay(1000);
}
}).GetAwaiter().OnCompleted(() =>
{
// Once the task for delaying is completed, check once more if cancellation is requested, as you will reach this point regardless of if it was cancelled or not.
if (!cancellationToken.IsCancellationRequested)
{
// Run this method again
RunOnSchedule(interval, cancellationToken);
}
});
}
In a WinForms application, when I want to wait on the main thread without blocking the app, I usually use
private void Wait (double milliseconds)
{
DateTime next = System.DateTime.Now.AddMilliseconds(milliseconds);
while (next > System.DateTime.Now)
Application.DoEvents();
}

Categories