Task.ContinueWith 不适用于 OnlyOnCanceled
Task.ContinueWith does not work with OnlyOnCanceled
微软70-483考试参考书中关于CancellationToken的使用,第一种用signal取消的方式是抛异常,然后介绍第二种:
Instead of catching the exception, you can also add a continuation
Task that executes only when the Task is canceled. In this Task, you
have access to the exception that was thrown, and you can choose to
handle it if that’s appropriate. Listing 1-44 shows what such a
continuation task would look like
这是列表 1-44:
Task task = Task.Run(() =>
{
while (!token.IsCancellationRequested)
{
Console.Write("*");
Thread.Sleep(1000);
}
}, token).ContinueWith((t) =>
{
t.Exception.Handle((e) => true);
Console.WriteLine("You have canceled the task");
}, TaskContinuationOptions.OnlyOnCanceled);
这是我完整的主要方法代码:
static void Main(string[] args)
{
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
var token = cancellationTokenSource.Token;
Task task = Task.Run(() =>
{
while (!token.IsCancellationRequested)
{
Console.Write("*");
Thread.Sleep(1000);
}
}, token).ContinueWith((t) =>
{
t.Exception.Handle((e) => true);
Console.WriteLine("You have canceled the task");
}, TaskContinuationOptions.OnlyOnCanceled);
Console.ReadLine();
cancellationTokenSource.Cancel();
task.Wait();
Console.ReadLine();
}
然而,与所说的不同,当我按 Enter 时,异常 (AggregationException) 仍然抛给 task.Wait()
调用的 Main 方法。此外,如果我删除该调用,则第二个 Task 永远不会运行(不会抛出异常)。我做错了什么吗?是否可以在不使用 try-catch
的情况下处理异常?
试试这个:
Task task = Task.Run(() =>
{
while (true) {
token.ThrowIfCancellationRequested();
Console.Write("*");
Thread.Sleep(1000);
}
}, token).ContinueWith((t) =>
{
//t.Exception.Handle((e) => true); //there is no exception
Console.WriteLine("You have canceled the task");
}, TaskContinuationOptions.OnlyOnCanceled);
使用 cancellationTokenSource.Cancel()
取消的任务实例将具有 TaskStatus.RanToCompletion
状态,而不是 TaskStatus.Canceled
状态。所以我认为您必须将 TaskContinuationOptions.OnlyOnCanceled
更改为 TaskContinuationOptions.OnlyOnRanToCompletion
您可以在 MSDN 上查看 Task Cancellation 了解更多详情。
这是示例代码:
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
var token = cancellationTokenSource.Token;
Task task = Task.Run(() =>
{
while (!token.IsCancellationRequested)
{
Console.Write("*");
Thread.Sleep(1000);
}
}, token).ContinueWith((t) =>
{
t.Exception.Handle((e) => true);
Console.WriteLine("You have canceled the task");
}, TaskContinuationOptions.OnlyOnRanToCompletion);
Console.ReadLine();
cancellationTokenSource.Cancel();
try
{
task.Wait();
}
catch (AggregateException e)
{
foreach (var v in e.InnerExceptions)
Console.WriteLine(e.Message + " " + v.Message);
}
Console.ReadLine();
要明确说明问题,您的第二个延续未执行,但您认为应该执行:
static void Main(string[] args)
{
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
var token = cancellationTokenSource.Token;
Task task = Task.Run(() =>
{
while (!token.IsCancellationRequested)
{
Console.Write("*");
Thread.Sleep(1000);
}
}, token).ContinueWith((t) =>
{ // THIS
t.Exception.Handle((e) => true); // ISN'T
Console.WriteLine("You have canceled the task"); // EXECUTING
}, TaskContinuationOptions.OnlyOnCanceled);
Console.ReadLine();
cancellationTokenSource.Cancel();
task.Wait();
Console.ReadLine();
}
第二个继续未执行,因为您必须使用 token.ThrowIfCancellationRequested()
才能触发它:
Task task = Task.Run(() =>
{
while (true)
{
token.ThrowIfCancellationRequested(); // <-- NOTICE
Console.Write("*");
Thread.Sleep(1000);
}
}, token).ContinueWith((t) =>
{
Console.WriteLine("From Continuation: " + t.Status);
Console.WriteLine("You have canceled the task");
}, TaskContinuationOptions.OnlyOnCanceled);
// OUTPUT:
// ***
// From Continuation: Canceled
// You have canceled the task
调用了第二个延续 ,因为 task.Status
是 Canceled
。下一个片段不会触发第二次继续,因为 task.Status
未设置为 Canceled
:
Task task = Task.Run(() =>
{
while (!token.IsCancellationRequested)
{
Console.Write("*");
Thread.Sleep(1000);
}
}, token).ContinueWith((t) =>
{
Console.WriteLine("From Continuation: " + t.Status);
Console.WriteLine("You have canceled the task");
}, TaskContinuationOptions.OnlyOnCanceled);
// OUTPUT:
// AggregationException
如前所述,未调用第二个延续。让我们通过删除 OnlyOnCanceled
子句来强制执行它:
Task task = Task.Run(() =>
{
while (!token.IsCancellationRequested)
{
Console.Write("*");
Thread.Sleep(1000);
}
}, token).ContinueWith((t) =>
{
Console.WriteLine("From Continuation: " + t.Status);
Console.WriteLine("You have NOT canceled the task");
}); // <-- OnlyOnCanceled is gone!
// OUTPUT:
// ***
// From Continuation: RanToCompletion
// You have NOT canceled the task
// (no AggregationException thrown)
请注意,即使调用了 .Cancel()
,延续中的 task.Status
也是 RanToCompletion
。还要注意没有 AggregationException
被抛出。这表明 仅从令牌源调用 .Cancel()
不会将任务状态设置为 Canceled
。
当只调用.Cancel()
而不调用.ThrowIfCancellationRequested()
时,AggregationException
实际上是任务取消成功的标志。引用 MSDN article:
If you are waiting on a Task
that transitions to the Canceled
state, a System.Threading.Tasks.TaskCanceledException
exception (wrapped in an AggregateException
exception) is thrown. Note that this exception indicates successful cancellation instead of a faulty situation. Therefore, the task's Exception
property returns null
.
这让我得出了一个宏大的结论:
清单 1-44 是一个 known error.
你的 t.Exception...
行已从我的所有代码中省略,因为“任务的 Exception
属性 returns null
" 成功取消后。 should 行已从清单 1-44 中省略。看起来他们将以下两种技术混为一谈:
- 我回答的第一段是取消任务的有效方法。
OnlyOnCanceled
延续被调用并且没有抛出异常。
- 我的回答的第二个片段也是取消任务的有效方法,但未调用
OnlyOnCanceled
延续,并抛出 AggregationException 供您在 Task.Wait()
[=76 处处理=]
免责声明:这两个片段都是取消任务的有效方法,但它们可能存在我不知道的行为差异。
微软70-483考试参考书中关于CancellationToken的使用,第一种用signal取消的方式是抛异常,然后介绍第二种:
Instead of catching the exception, you can also add a continuation Task that executes only when the Task is canceled. In this Task, you have access to the exception that was thrown, and you can choose to handle it if that’s appropriate. Listing 1-44 shows what such a continuation task would look like
这是列表 1-44:
Task task = Task.Run(() =>
{
while (!token.IsCancellationRequested)
{
Console.Write("*");
Thread.Sleep(1000);
}
}, token).ContinueWith((t) =>
{
t.Exception.Handle((e) => true);
Console.WriteLine("You have canceled the task");
}, TaskContinuationOptions.OnlyOnCanceled);
这是我完整的主要方法代码:
static void Main(string[] args)
{
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
var token = cancellationTokenSource.Token;
Task task = Task.Run(() =>
{
while (!token.IsCancellationRequested)
{
Console.Write("*");
Thread.Sleep(1000);
}
}, token).ContinueWith((t) =>
{
t.Exception.Handle((e) => true);
Console.WriteLine("You have canceled the task");
}, TaskContinuationOptions.OnlyOnCanceled);
Console.ReadLine();
cancellationTokenSource.Cancel();
task.Wait();
Console.ReadLine();
}
然而,与所说的不同,当我按 Enter 时,异常 (AggregationException) 仍然抛给 task.Wait()
调用的 Main 方法。此外,如果我删除该调用,则第二个 Task 永远不会运行(不会抛出异常)。我做错了什么吗?是否可以在不使用 try-catch
的情况下处理异常?
试试这个:
Task task = Task.Run(() =>
{
while (true) {
token.ThrowIfCancellationRequested();
Console.Write("*");
Thread.Sleep(1000);
}
}, token).ContinueWith((t) =>
{
//t.Exception.Handle((e) => true); //there is no exception
Console.WriteLine("You have canceled the task");
}, TaskContinuationOptions.OnlyOnCanceled);
使用 cancellationTokenSource.Cancel()
取消的任务实例将具有 TaskStatus.RanToCompletion
状态,而不是 TaskStatus.Canceled
状态。所以我认为您必须将 TaskContinuationOptions.OnlyOnCanceled
更改为 TaskContinuationOptions.OnlyOnRanToCompletion
您可以在 MSDN 上查看 Task Cancellation 了解更多详情。
这是示例代码:
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
var token = cancellationTokenSource.Token;
Task task = Task.Run(() =>
{
while (!token.IsCancellationRequested)
{
Console.Write("*");
Thread.Sleep(1000);
}
}, token).ContinueWith((t) =>
{
t.Exception.Handle((e) => true);
Console.WriteLine("You have canceled the task");
}, TaskContinuationOptions.OnlyOnRanToCompletion);
Console.ReadLine();
cancellationTokenSource.Cancel();
try
{
task.Wait();
}
catch (AggregateException e)
{
foreach (var v in e.InnerExceptions)
Console.WriteLine(e.Message + " " + v.Message);
}
Console.ReadLine();
要明确说明问题,您的第二个延续未执行,但您认为应该执行:
static void Main(string[] args)
{
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
var token = cancellationTokenSource.Token;
Task task = Task.Run(() =>
{
while (!token.IsCancellationRequested)
{
Console.Write("*");
Thread.Sleep(1000);
}
}, token).ContinueWith((t) =>
{ // THIS
t.Exception.Handle((e) => true); // ISN'T
Console.WriteLine("You have canceled the task"); // EXECUTING
}, TaskContinuationOptions.OnlyOnCanceled);
Console.ReadLine();
cancellationTokenSource.Cancel();
task.Wait();
Console.ReadLine();
}
第二个继续未执行,因为您必须使用 token.ThrowIfCancellationRequested()
才能触发它:
Task task = Task.Run(() =>
{
while (true)
{
token.ThrowIfCancellationRequested(); // <-- NOTICE
Console.Write("*");
Thread.Sleep(1000);
}
}, token).ContinueWith((t) =>
{
Console.WriteLine("From Continuation: " + t.Status);
Console.WriteLine("You have canceled the task");
}, TaskContinuationOptions.OnlyOnCanceled);
// OUTPUT:
// ***
// From Continuation: Canceled
// You have canceled the task
调用了第二个延续 ,因为 task.Status
是 Canceled
。下一个片段不会触发第二次继续,因为 task.Status
未设置为 Canceled
:
Task task = Task.Run(() =>
{
while (!token.IsCancellationRequested)
{
Console.Write("*");
Thread.Sleep(1000);
}
}, token).ContinueWith((t) =>
{
Console.WriteLine("From Continuation: " + t.Status);
Console.WriteLine("You have canceled the task");
}, TaskContinuationOptions.OnlyOnCanceled);
// OUTPUT:
// AggregationException
如前所述,未调用第二个延续。让我们通过删除 OnlyOnCanceled
子句来强制执行它:
Task task = Task.Run(() =>
{
while (!token.IsCancellationRequested)
{
Console.Write("*");
Thread.Sleep(1000);
}
}, token).ContinueWith((t) =>
{
Console.WriteLine("From Continuation: " + t.Status);
Console.WriteLine("You have NOT canceled the task");
}); // <-- OnlyOnCanceled is gone!
// OUTPUT:
// ***
// From Continuation: RanToCompletion
// You have NOT canceled the task
// (no AggregationException thrown)
请注意,即使调用了 .Cancel()
,延续中的 task.Status
也是 RanToCompletion
。还要注意没有 AggregationException
被抛出。这表明 仅从令牌源调用 .Cancel()
不会将任务状态设置为 Canceled
。
当只调用.Cancel()
而不调用.ThrowIfCancellationRequested()
时,AggregationException
实际上是任务取消成功的标志。引用 MSDN article:
If you are waiting on a
Task
that transitions to theCanceled
state, aSystem.Threading.Tasks.TaskCanceledException
exception (wrapped in anAggregateException
exception) is thrown. Note that this exception indicates successful cancellation instead of a faulty situation. Therefore, the task'sException
property returnsnull
.
这让我得出了一个宏大的结论:
清单 1-44 是一个 known error.
你的 t.Exception...
行已从我的所有代码中省略,因为“任务的 Exception
属性 returns null
" 成功取消后。 should 行已从清单 1-44 中省略。看起来他们将以下两种技术混为一谈:
- 我回答的第一段是取消任务的有效方法。
OnlyOnCanceled
延续被调用并且没有抛出异常。 - 我的回答的第二个片段也是取消任务的有效方法,但未调用
OnlyOnCanceled
延续,并抛出 AggregationException 供您在Task.Wait()
[=76 处处理=]
免责声明:这两个片段都是取消任务的有效方法,但它们可能存在我不知道的行为差异。