我很难绕着扩展方法缠头。它们是静态类中的静态方法。它们是如何在内部初始化的?例如,我写了下面的扩展方法。它是线程安全吗?
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();
}
}
答案 0 :(得分:3)
所有扩展方法都是转
var result = myTask.TimeoutAfter(TimeSpan.FromSecconds(5));
进入
var result = ExtensionMethodClass.TimeoutAfter(myTask, TimeSpan.FromSecconds(5));
,没有别的。因此,函数是否是一个扩展方法根本不会影响它的行为,这只是程序员不必从上面的例子中输入长版本的说服力。
至于你的代码是否是线程安全的,首先你需要了解“线程安全”的含义。我强烈建议您阅读Eric Lippert撰写的文章“What is this thing you call "thread safe"?”,它将极大地帮助您了解线程安全的含义。
您的代码不会从其作用域访问或改变任何外部变量,因此函数本身是线程安全的,但这并不意味着它不能以“线程不安全”的方式使用。幸运的是,您很幸运,Task
和TimeSpan
都保证了所有方法和属性的安全性,因此您不太可能遇到任何线程安全问题。
然而,所有这一切,你都有一个与竞争条件有关的错误。如果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();
}
}
}