取消令牌在这里有什么用
What is the use of Cancellation token here
我有以下代码块:
using (var cancelSource = new CancellationTokenSource())
{
Task[] tasks = null;
var cancelToken = cancelSource.Token;
tasks = new[]
{
Task.Run(async () => await ThrowAfterAsync("C", cancelToken, 1000)) //<---
};
await Task.Delay(howLongSecs * 1000); // <---
cancelSource.Cancel();
await Task.WhenAll(tasks);
}
其中 ThrowAfterAsync
有这个:
private async Task ThrowAfterAsync(string taskId, CancellationToken cancelToken, int afterMs)
{
await Task.Delay(afterMs, cancelToken);
var msg = $"{taskId} throwing after {afterMs}ms";
Console.WriteLine(msg);
throw new ApplicationException(msg);
}
Resharper 建议我可以将 Task.Run()
的重载与取消标记一起使用,如下所示:
Task.Run(async () => await ThrowAfterAsync("C", cancelToken, 1000), cancelToken)
但是为什么呢?在没有取消令牌作为参数的情况下,在第一个版本上这样做有什么好处?
在这种特定情况下,没有意义。一般来说,你会想按照它的建议去做,因为通过将令牌传递给 Task.Run
如果在操作甚至有机会 [=] 之前取消令牌,它甚至可以避免首先安排操作26=]start,但在您的情况下,您正在创建令牌并且您知道它不会在您开始操作时被取消。
但是您不需要将令牌传递给 Task.Run
的原因是因为启动该任务的代码 是负责取消令牌的操作 ,并且所以它知道令牌尚未取消。通常你会接受来自其他地方的令牌,你不会知道 if/when 它已被取消。
综上所述,根本没有理由使用 Task.Run
。你可以只写:
tasks = new[] { ThrowAfterAsync("C", cancelToken, 1000) };
它将具有相同的行为,但不会为了启动异步操作而不必要地启动一个新线程。
接下来,您的代码 永远不会 return 少于 howLongSecs
秒 ,即使操作在此之前完成,因为您已经结构化你的代码。您应该简单地向取消令牌源提供超时并让它在正确的时间取消令牌,如果操作在取消应该发生之前完成,它不会延迟您的方法的其余部分,因此您的整个方法可以只需写成:
using (var cancelSource = new CancellationTokenSource(Timespan.FromSeconds(howLongSecs)))
{
await ThrowAfterAsync("C", cancelToken, 1000)
}
Resharper 发现您使用的方法 (Task.Run
) 具有接受 CancellationToken
的重载,您在范围内有 CancellationToken
的实例,但您没有使用该重载它接受一个令牌。它不会对您的代码执行任何广泛的分析——就这么简单。您可以使用以下代码轻松验证这一点:
class Program {
static void Main() {
CancellationToken ct;
Test("msg"); // will suggest to pass CancellationToken here
}
private static void Test(string msg) {
}
private static void Test(string msg, CancellationToken ct) {
}
}
是的,代码本身很奇怪,您根本不需要将异步包装在 Task.Run
中,但我不会触及它,因为您问的只是为什么 Resharper 建议这样做。
我有以下代码块:
using (var cancelSource = new CancellationTokenSource())
{
Task[] tasks = null;
var cancelToken = cancelSource.Token;
tasks = new[]
{
Task.Run(async () => await ThrowAfterAsync("C", cancelToken, 1000)) //<---
};
await Task.Delay(howLongSecs * 1000); // <---
cancelSource.Cancel();
await Task.WhenAll(tasks);
}
其中 ThrowAfterAsync
有这个:
private async Task ThrowAfterAsync(string taskId, CancellationToken cancelToken, int afterMs)
{
await Task.Delay(afterMs, cancelToken);
var msg = $"{taskId} throwing after {afterMs}ms";
Console.WriteLine(msg);
throw new ApplicationException(msg);
}
Resharper 建议我可以将 Task.Run()
的重载与取消标记一起使用,如下所示:
Task.Run(async () => await ThrowAfterAsync("C", cancelToken, 1000), cancelToken)
但是为什么呢?在没有取消令牌作为参数的情况下,在第一个版本上这样做有什么好处?
在这种特定情况下,没有意义。一般来说,你会想按照它的建议去做,因为通过将令牌传递给 Task.Run
如果在操作甚至有机会 [=] 之前取消令牌,它甚至可以避免首先安排操作26=]start,但在您的情况下,您正在创建令牌并且您知道它不会在您开始操作时被取消。
但是您不需要将令牌传递给 Task.Run
的原因是因为启动该任务的代码 是负责取消令牌的操作 ,并且所以它知道令牌尚未取消。通常你会接受来自其他地方的令牌,你不会知道 if/when 它已被取消。
综上所述,根本没有理由使用 Task.Run
。你可以只写:
tasks = new[] { ThrowAfterAsync("C", cancelToken, 1000) };
它将具有相同的行为,但不会为了启动异步操作而不必要地启动一个新线程。
接下来,您的代码 永远不会 return 少于 howLongSecs
秒 ,即使操作在此之前完成,因为您已经结构化你的代码。您应该简单地向取消令牌源提供超时并让它在正确的时间取消令牌,如果操作在取消应该发生之前完成,它不会延迟您的方法的其余部分,因此您的整个方法可以只需写成:
using (var cancelSource = new CancellationTokenSource(Timespan.FromSeconds(howLongSecs)))
{
await ThrowAfterAsync("C", cancelToken, 1000)
}
Resharper 发现您使用的方法 (Task.Run
) 具有接受 CancellationToken
的重载,您在范围内有 CancellationToken
的实例,但您没有使用该重载它接受一个令牌。它不会对您的代码执行任何广泛的分析——就这么简单。您可以使用以下代码轻松验证这一点:
class Program {
static void Main() {
CancellationToken ct;
Test("msg"); // will suggest to pass CancellationToken here
}
private static void Test(string msg) {
}
private static void Test(string msg, CancellationToken ct) {
}
}
是的,代码本身很奇怪,您根本不需要将异步包装在 Task.Run
中,但我不会触及它,因为您问的只是为什么 Resharper 建议这样做。