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();
}
}
}
}
CodePudding user response:
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.
CodePudding user response:
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();