的任务.ContinueWith不能与onlyoncancelled一起工作
本文关键字:onlyoncancelled 一起 工作 不能 任务 ContinueWith | 更新日期: 2023-09-27 18:18:04
在Microsoft关于使用CancellationToken的70-483考试的参考书中,用信号取消的第一种方法是抛出异常,然后引入第二种方法:
除了捕获异常,还可以添加一个延续只在取消任务时执行的任务。在这个任务中,你是否有权访问被抛出的异常,您可以选择如果合适的话处理它。清单1-44显示了这样一个延续任务看起来像
以下是列表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);
这是我完整的Main方法代码:
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
是否可以处理异常?
要明确说明问题,您的第二个continuation没有执行,但您认为应该执行:
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()
,但是continuation中的task.Status
是RanToCompletion
。还要注意没有抛出AggregationException
。这表明仅仅从令牌源调用.Cancel()
并没有将任务状态设置为Canceled
。
当只调用.Cancel()
而不调用.ThrowIfCancellationRequested()
时,AggregationException
实际上是任务取消成功的一个指标。引用MSDN文章:
如果您正在等待
Task
转换到Canceled
状态,则会抛出System.Threading.Tasks.TaskCanceledException
异常(包装在AggregateException
异常中)。注意,此异常表示取消成功,而不是错误情况。因此,任务的Exception
属性返回null
。
这让我得出了一个伟大的结论:
清单1-44是一个已知的错误。您的t.Exception...
行已从我的所有代码中省略,因为"任务的Exception
属性在成功取消后返回null
"。清单1-44中应该省略行。看起来他们把以下两种技术混为一谈了:
- 我的答案的第一个片段是取消任务的有效方法。
OnlyOnCanceled
的延续被调用并且不抛出异常。 - 我的答案的第二个片段也是一个有效的方式来取消一个任务,但
OnlyOnCanceled
延续不被调用,并抛出一个AggregationException为您处理在Task.Wait()
免责声明:这两个片段都是取消任务的有效方法,但它们可能在行为上有我不知道的差异。
使用cancellationTokenSource.Cancel()
取消的任务实例将具有TaskStatus.RanToCompletion
状态,而不是TaskStatus.Canceled
状态。所以我认为你应该把TaskContinuationOptions.OnlyOnCanceled
改成TaskContinuationOptions.OnlyOnRanToCompletion
您可以在MSDN上查看任务取消以获取更多详细信息。
下面是示例代码:
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();
试试这个:
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);