这个扩展方法头安全吗?
本文关键字:安全 方法 扩展 | 更新日期: 2023-09-27 18:03:03
我很难理解扩展方法。它们是静态类中的静态方法。它们是如何在内部初始化的?例如,我编写了下面的扩展方法。线程安全吗?
public static async Task TimeoutAfter(this Task task, TimeSpan timeSpan)
{
var cts = new CancellationTokenSource();
try
{
if (task.IsCompleted || timeSpan == Timeout.InfiniteTimeSpan)
return;
if (timeSpan == TimeSpan.Zero)
throw new TimeoutException();
if (task == await Task.WhenAny(task, Task.Delay(timeSpan, cts.Token)))
{
cts.Cancel();
await task;
}
else
{
throw new TimeoutException();
}
}
finally
{
cts.Dispose();
}
}
所有扩展方法所做的就是转
var result = myTask.TimeoutAfter(TimeSpan.FromSecconds(5));
in to
var result = ExtensionMethodClass.TimeoutAfter(myTask, TimeSpan.FromSecconds(5));
,没有别的了。因此,一个函数是否是一个扩展方法根本不会影响它的行为,它只是一个说服,让程序员不必从上面的例子中输入长版本。
至于你的代码是否线程安全,首先你需要理解"线程安全"是什么意思。我强烈建议您阅读Eric Lippert的文章"您称之为"线程安全"的东西是什么?",它将极大地帮助您理解线程安全的含义。你的代码不访问或改变任何外部变量从它的作用域,所以函数本身是线程安全的,但这并不意味着它不能在"线程不安全"的方式使用。幸运的是,Task
和TimeSpan
在它们所有的方法和属性中都保证了线程安全,所以你不太可能遇到任何线程安全问题。
不管怎么说,你确实有一个竞争条件的bug。如果task.IsCompleted
返回true并且task
抛出异常,您将永远不会收到该异常的通知。此外,如果timeSpan == Timeout.InfiniteTimeSpan
,您的函数将立即返回一个完成的任务,即使传入的任务仍在运行。即使您不打算执行超时,也需要执行await
任务。另外,try/finally可以简化为using
语句
public static async Task TimeoutAfter(this Task task, TimeSpan timeSpan)
{
using(var cts = new CancellationTokenSource())
{
if (task.IsCompleted || timeSpan == Timeout.InfiniteTimeSpan)
{
await task;
return;
}
if (timeSpan == TimeSpan.Zero)
throw new TimeoutException();
if (task == await Task.WhenAny(task, Task.Delay(timeSpan, cts.Token)))
{
cts.Cancel();
await task;
}
else
{
throw new TimeoutException();
}
}
}
最后,如果你还没有这样做,你会想要做一个接受Task<T>
的版本,以防你想超时返回结果的任务。
public static async Task<T> TimeoutAfter<T>(this Task<T> task, TimeSpan timeSpan)
{
using(var cts = new CancellationTokenSource())
{
if (task.IsCompleted || timeSpan == Timeout.InfiniteTimeSpan)
{
return await task
}
if (timeSpan == TimeSpan.Zero)
throw new TimeoutException();
if (task == await Task.WhenAny(task, Task.Delay(timeSpan, cts.Token)))
{
cts.Cancel();
return await task;
}
else
{
throw new TimeoutException();
}
}
}