我有以下代码:
configure(rootProject.subprojects.findAll {
it.configurations.getByName("compile")
.dependencies.any {
it.name == project.name
}
}) {
dependencies {
testCompile project.sourceSets.test.output
}
}
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
Resharper建议我可以使用带有取消令牌的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);
}
重载:
Task.Run()
但为什么呢?在没有取消令牌作为参数的情况下,在第一个版本上执行此操作有什么好处?
答案 0 :(得分:5)
在这种特定情况下,没有意义。一般情况下,您希望按照建议进行操作,因为通过将令牌传递给Task.Run
,如果在操作之前取消令牌甚至有机会,则可以避免在第一时间安排操作开始,但在您的情况下,您正在创建令牌并且知道它在您开始操作时不会被取消。
但是您不需要将令牌传递给Task.Run
的原因是因为启动该任务的代码是负责取消令牌的操作,因此它知道令牌尚未取消。通常情况下,您可以从其他地方接受令牌,而且您不知道是否/何时取消令牌。
所有这一切,都没有理由甚至根本不使用Task.Run
。你可以写:
tasks = new[] { ThrowAfterAsync("C", cancelToken, 1000) };
它将具有相同的行为,但不必为了启动异步操作而不必要地启动新线程。
接下来,您的代码将永远不会在howLongSecs
秒内返回,即使操作在此之前完成,因为您的代码构造方式也是如此。你应该简单地为取消令牌源提供超时并让它在合适的时间处理取消令牌,如果操作在取消之前完成,它将不会延迟你的方法的其余部分,所以你的整个方法可以写成:
using (var cancelSource = new CancellationTokenSource(Timespan.FromSeconds(howLongSecs)))
{
await ThrowAfterAsync("C", cancelToken, 1000)
}
答案 1 :(得分:2)
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建议这样做。