我有两个例子,直接来自microsoft,这些示例似乎与取消令牌无关,因为我可以删除提供给任务的令牌,结果是相同的。所以我的问题是:什么是取消令牌,以及为什么这些可怜的例子?我错过了什么......? :)
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.cancellationtoken(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();
}
}
}
答案 0 :(得分:3)
由于.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
的令牌匹配。
答案 1 :(得分:0)
在我找到这个问题之前,我正要问一个类似的问题。来自i3arnon的答案是有道理的,但我会将这个答案添加为有希望帮助某人的补充。
我首先要说的是(与接受的答案的评论相反)微软在MSDN上的例子非常糟糕。除非您已经知道取消如何运作,否则它们对您没有多大帮助。 This MSDN article向您展示了如何将CancellationToken
传递给Task
,但如果您按照示例操作,他们实际上从未向您展示如何取消当前正在执行的您自己的 { {1}}。 Task
只是消失在Microsoft代码中:
CancellationToken
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