这个扩展方法线程安全吗?
Is this extension method thead safe?
我很难理解扩展方法。它们是 static 类 中的静态方法。它们是如何在内部初始化的?例如,我写了下面的扩展方法。它是线程安全的吗?
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));
到
var result = ExtensionMethodClass.TimeoutAfter(myTask, TimeSpan.FromSecconds(5));
仅此而已。所以一个函数是否是一个扩展方法根本不会影响它的行为,它只是说服程序员不必从我上面的例子中输入长版本。
至于你的代码是不是线程安全的,首先你要明白"thread safe"是什么意思。我强烈推荐您阅读 Eric Lippert 的文章“What is this thing you call "thread safe"?”,这将极大地帮助您理解线程安全的含义。
您的代码不会访问或改变其范围内的任何外部变量,因此该函数本身是线程安全的,但这并不意味着它不能以 "thread unsafe" 方式使用。幸运的是你很幸运,Task
和 TimeSpan
都保证了它们所有方法和属性的线程安全,所以你 运行 不太可能遇到任何线程安全问题。
尽管如此,您确实遇到了竞争条件错误。如果 task.IsCompleted
return 为真并且 task
抛出异常,您将永远不会收到该异常的通知。此外,如果 timeSpan == Timeout.InfiniteTimeSpan
您的函数将立即 return 完成任务,即使传入的任务仍然是 运行ning。即使您不打算超时,也需要 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>
的版本,以防您想要使 return 结果超时的任务。
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();
}
}
}
我很难理解扩展方法。它们是 static 类 中的静态方法。它们是如何在内部初始化的?例如,我写了下面的扩展方法。它是线程安全的吗?
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));
到
var result = ExtensionMethodClass.TimeoutAfter(myTask, TimeSpan.FromSecconds(5));
仅此而已。所以一个函数是否是一个扩展方法根本不会影响它的行为,它只是说服程序员不必从我上面的例子中输入长版本。
至于你的代码是不是线程安全的,首先你要明白"thread safe"是什么意思。我强烈推荐您阅读 Eric Lippert 的文章“What is this thing you call "thread safe"?”,这将极大地帮助您理解线程安全的含义。
您的代码不会访问或改变其范围内的任何外部变量,因此该函数本身是线程安全的,但这并不意味着它不能以 "thread unsafe" 方式使用。幸运的是你很幸运,Task
和 TimeSpan
都保证了它们所有方法和属性的线程安全,所以你 运行 不太可能遇到任何线程安全问题。
尽管如此,您确实遇到了竞争条件错误。如果 task.IsCompleted
return 为真并且 task
抛出异常,您将永远不会收到该异常的通知。此外,如果 timeSpan == Timeout.InfiniteTimeSpan
您的函数将立即 return 完成任务,即使传入的任务仍然是 运行ning。即使您不打算超时,也需要 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>
的版本,以防您想要使 return 结果超时的任务。
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();
}
}
}