这个扩展方法线程安全吗?

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" 方式使用。幸运的是你很幸运,TaskTimeSpan 都保证了它们所有方法和属性的线程安全,所以你 运行 不太可能遇到任何线程安全问题。


尽管如此,您确实遇到了竞争条件错误。如果 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();
        }
    }
}