CancellationToken的默认参数

165

我有一些异步代码,想要添加一个CancellationToken。然而,并非所有情况都需要使用此功能,因此我想设置一个默认参数——可能是CancellationToken.None。但是,

Task<x> DoStuff(...., CancellationToken ct = null)

产生

类型为“<null>”的值不能用作默认参数,因为没有标准转换到类型“System.Threading.CancellationToken”。

Task<x> DoStuff(...., CancellationToken ct = CancellationToken.None)

'ct'的默认参数值必须是编译时常量

有没有办法为CancellationToken设置默认值?


1
我也看到过 new CancellationToken(),它与 default 完全等效,因为 CancellationToken 是一个结构体。 - Palec
为什么有人会想要这个?即使在调用方法中有它,也很容易忘记传递“CancelationToken”。另一方面,如果你没有它,输入CancellationToken.None也不是那么困难吧? - Alexei Sosin
为什么有人会想要这个?即使在调用方法中有它,也很容易忘记传递“CancelationToken”。另一方面,如果你没有它,输入CancellationToken.None也不是那么困难吧? - undefined
5个回答

252

结果证明以下内容是有效的:

Task<x> DoStuff(...., CancellationToken ct = default(CancellationToken))

...or:

Task<x> DoStuff(...., CancellationToken ct = default) // C# 7.1 and later

根据文档CancellationToken.None与之相同:

你也可以使用C#语言中的default(CancellationToken)语句创建一个空的取消令牌。


4
这正是该框架当前所做的,但我不会在我的代码中这样做。想想如果 Microsoft 改变他们的实现,CancellationToken.None 变成了不同于 default(CancellationToken) 的东西,你的代码会发生什么。 - noseratio - open to work
22
@Noseratio:那样做将会大幅违反向后兼容性,所以我不认为会发生这种情况。default(CancellationToken) 还能做什么? Translated: @Noseratio:这将会严重破坏向后兼容性,因此我不认为会发生这种情况。default(CancellationToken) 还能有什么其他作用? - svick
4
@Noseratio: 你太死板了。很有可能CancellationToken.None会成为事实上的弃用。甚至微软也开始使用 default(CancellationToken)。例如,可以参考这些搜索结果来自 Entity Framework 的源代码。 - drowa
25
从MSDN CancellationToken.None Property中可以得知:"你也可以使用C#默认的CancellationToken语句来创建一个空的取消标记"。直到将来的C#版本接受None作为默认参数之前,None是一个错误。 - MuiBienCarlota
5
在这里也是一样。为了弥补两个中间组合的缺失,开发人员可以将cancellationToken参数传递为None或默认的CancellationToken,并将进度参数设为null。 - Arek Bal
显示剩余3条评论

29

以下是几种解决方案,按照通用性从高到低排列:

1. 使用 default(CancellationToken) 作为默认值:

Task DoAsync(CancellationToken ct = default(CancellationToken)) { … }

从语义上讲,CancellationToken.None 将是默认选择的理想候选项,但由于它不是编译时常量,因此不能用作这样的常量。 default(CancellationToken) 是次佳选择,因为它是编译时常量,并且官方文档已正式记录其等效于CancellationToken.None

2. 提供一个没有 CancellationToken 参数的方法重载:

或者,如果您更喜欢方法重载而不是可选参数(请参见有关该主题的问题):

Task DoAsync(CancellationToken ct) { … } // actual method always requires a token
Task DoAsync() => DoAsync(CancellationToken.None); // overload producing a default token

对于接口方法,可以使用扩展方法来实现:

interface IFoo
{
    Task DoAsync(CancellationToken ct);
}

static class Foo
{
    public static Task DoAsync(this IFoo foo) => foo.DoAsync(CancellationToken.None);
}

这导致了更简洁的界面,使得实现者不需要显式地编写转发方法重载。

3. 使参数可为空并将 null 用作默认值:

Task DoAsync(…, CancellationToken? ct = null)
{
    … ct ?? CancellationToken.None …
}

我最不喜欢这个解决方案,因为可空类型会带来一些运行时开销,并且由于 null 合并运算符 ?? 的存在,对取消标记的引用变得更加冗长。


26

是否有办法为CancellationToken设置默认值?

不幸的是,这是不可能的,因为CancellationToken.None不是编译时常量,而这是可选参数默认值的要求。

然而,你可以通过创建重载方法来实现相同的效果,而不是尝试使用默认参数:

Task<x> DoStuff(...., CancellationToken ct)
{
    //...
}

Task<x> DoStuff(....)
{
    return DoStuff(...., CancellationToken.None);
}

6
这是建议的处理方式,如在MSDN上的“基于任务的异步模式”文档(特别是在选择要提供的重载部分)中所述。 - Sam Harwell
CancellationToken.None == 默认值 true - eoleary
8
“CancellationToken cancellationToken = default(CancellationToken)”是怎么回事?这里也有详细描述:https://blogs.msdn.microsoft.com/andrewarnottms/2014/03/19/recommended-patterns-for-cancellationtoken/。 - Ray

11

另一个选项是使用一个Nullable<CancellationToken>参数,将其默认值设置为null,并在方法内部处理它:

Task<x> DoStuff(...., CancellationToken? ct = null) {
    var token = ct ?? CancellationToken.None;
    ...
}

太棒了!这绝对是最好的答案。 - John Henckel
这与现在的C# 7.1以上提供默认值是一样的。最好只使用默认值。 - undefined

10
新版C#允许使用简化语法来调用默认的(CancellationToken)版本。例如:
Task<x> DoStuff(...., CancellationToken ct = default)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接