将取消令牌提供给任务不会执行任何操作
本文关键字:执行 任何 操作 任务 取消 令牌 | 更新日期: 2023-09-27 18:19:17
我有两个例子,直接来自微软,这些例子似乎与取消令牌无关,因为我可以删除提供给任务的令牌,结果是一样的。所以我的问题是:取消令牌有什么用,为什么是糟糕的例子?我错过了什么..?:)
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Chapter1.Threads
{
public class Program
{
static void Main()
{
CancellationTokenSource cancellationTokenSource =
new CancellationTokenSource();
CancellationToken token = cancellationTokenSource.Token;
Task task = Task.Run(() =>
{
while (!token.IsCancellationRequested)
{
Console.Write(“*”);
Thread.Sleep(1000);
}
token.ThrowIfCancellationRequested();
}, token);
try
{
Console.WriteLine(“Press enter to stop the task”);
Console.ReadLine();
cancellationTokenSource.Cancel();
task.Wait();
}
catch (AggregateException e)
{
Console.WriteLine(e.InnerExceptions[0].Message);
}
Console.WriteLine(“Press enter to end the application”);
Console.ReadLine();
}
}
}
代码示例 2:https://msdn.microsoft.com/en-us/library/system.threading.canceltoken(v=vs.110(.aspx
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
public class Example
{
public static void Main()
{
// Define the cancellation token.
CancellationTokenSource source = new CancellationTokenSource();
CancellationToken token = source.Token;
Random rnd = new Random();
Object lockObj = new Object();
List<Task<int[]>> tasks = new List<Task<int[]>>();
TaskFactory factory = new TaskFactory(token);
for (int taskCtr = 0; taskCtr <= 10; taskCtr++) {
int iteration = taskCtr + 1;
tasks.Add(factory.StartNew( () => {
int value;
int[] values = new int[10];
for (int ctr = 1; ctr <= 10; ctr++) {
lock (lockObj) {
value = rnd.Next(0,101);
}
if (value == 0) {
source.Cancel();
Console.WriteLine("Cancelling at task {0}", iteration);
break;
}
values[ctr-1] = value;
}
return values;
}, token));
}
try {
Task<double> fTask = factory.ContinueWhenAll(tasks.ToArray(),
(results) => {
Console.WriteLine("Calculating overall mean...");
long sum = 0;
int n = 0;
foreach (var t in results) {
foreach (var r in t.Result) {
sum += r;
n++;
}
}
return sum/(double) n;
} , token);
Console.WriteLine("The mean is {0}.", fTask.Result);
}
catch (AggregateException ae) {
foreach (Exception e in ae.InnerExceptions) {
if (e is TaskCanceledException)
Console.WriteLine("Unable to compute mean: {0}",
((TaskCanceledException) e).Message);
else
Console.WriteLine("Exception: " + e.GetType().Name);
}
}
finally {
source.Dispose();
}
}
}
由于 .Net 中的取消是合作性的,因此将CancellationToken
传递到Task.Run
例如不足以确保任务被取消。
作为参数传递只会将令牌与任务相关联。只有在令牌取消之前没有机会开始运行时,它才能取消任务。例如:
var token = new CancellationToken(true); // creates a cancelled token
Task.Run(() => {}, token);
要在"飞行途中"取消任务,您需要任务本身观察令牌并在发出取消信号时抛出,类似于:
Task.Run(() =>
{
while (true)
{
token.ThrowIfCancellationRequested();
// do something
}
}, token);
此外,简单地从任务内部抛出异常只会将任务标记为Faulted
。要将其标记为Cancelled
,TaskCanceledException.CancellationToken
需要匹配传递给Task.Run
的令牌。
我正要问一个类似的问题,直到我找到了这个问题。来自 i3arnon 的答案是有道理的,但我会添加这个答案作为补充,希望能帮助某人。
我首先要说(与对接受答案的评论相反(,MSDN 上Microsoft的例子很糟糕。除非您已经知道取消的工作原理,否则它们不会对您有很大帮助。这篇 MSDN 文章向您展示如何将CancellationToken
传递给Task
但是如果您按照这些示例进行操作,它们实际上从未向您展示如何取消您自己的当前正在执行的Task
。CancellationToken
只是消失在Microsoft代码中:
-
await client.GetAsync("http://msdn.microsoft.com/en-us/library/dd470362.aspx", ct);
-
await response.Content.ReadAsByteArrayAsync();
以下是我如何使用CancellationToken
的示例:
当我有一个需要不断重复的任务时:
public class Foo
{
private CancellationTokenSource _cts;
public Foo()
{
this._cts = new CancellationTokenSource();
}
public void StartExecution()
{
Task.Factory.StartNew(this.OwnCodeCancelableTask, this._cts.Token);
Task.Factory.StartNew(this.OwnCodeCancelableTask_EveryNSeconds, this._cts.Token);
}
public void CancelExecution()
{
this._cts.Cancel();
}
/// <summary>
/// "Infinite" loop with no delays. Writing to a database while pulling from a buffer for example.
/// </summary>
/// <param name="taskState">The cancellation token from our _cts field, passed in the StartNew call</param>
private void OwnCodeCancelableTask(object taskState)
{
var token = (CancellationToken) taskState;
while ( !token.IsCancellationRequested )
{
Console.WriteLine("Do your task work in this loop");
}
}
/// <summary>
/// "Infinite" loop that runs every N seconds. Good for checking for a heartbeat or updates.
/// </summary>
/// <param name="taskState">The cancellation token from our _cts field, passed in the StartNew call</param>
private async void OwnCodeCancelableTask_EveryNSeconds(object taskState)
{
var token = (CancellationToken)taskState;
while (!token.IsCancellationRequested)
{
Console.WriteLine("Do the work that needs to happen every N seconds in this loop");
// Passing token here allows the Delay to be cancelled if your task gets cancelled.
await Task.Delay(1000 /*Or however long you want to wait.*/, token);
}
}
}
当我有用户可以启动的任务时:
public class Foo
{
private CancellationTokenSource _cts;
private Task _taskWeCanCancel;
public Foo()
{
this._cts = new CancellationTokenSource();
//This is where it's confusing. Passing the token here will only ensure that the task doesn't
//run if it's canceled BEFORE it starts. This does not cancel the task during the operation of our code.
this._taskWeCanCancel = new Task(this.FireTheTask, this._cts.Token);
}
/// <summary>
/// I'm not a fan of returning tasks to calling code, so I keep this method void
/// </summary>
public void FireTheTask()
{
//Check task status here if it's required.
this._taskWeCanCancel.Start();
}
public void CancelTheTask()
{
this._cts.Cancel();
}
/// <summary>
/// Go and get something from the web, process a piece of data, execute a lengthy calculation etc...
/// </summary>
private async void OurTask()
{
Console.WriteLine("Do your work here and check periodically for task cancellation requests...");
if (this._cts.Token.IsCancellationRequested) return;
Console.WriteLine("Do another step to your work here then check the token again if necessary...");
if (this._cts.Token.IsCancellationRequested) return;
Console.WriteLine("Some work that we need to delegate to another task");
await Some.Microsoft.Object.DoStuffAsync();
}
}
也许我错过了Task
的一些关键特征,但是将CancellationToken
传递给Task
,而不是状态之外的任何东西对我来说从来没有多大意义。我还没有遇到过这样一种情况:我将CancellationToken
传递给Task
并在运行之前取消了Task
,即使我这样做了,我创建的每个任务中的第一行总是
if (token.IsCancellationRequested) return;