这个扩展方法头安全吗?

本文关键字:安全 方法 扩展 | 更新日期: 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的文章"您称之为"线程安全"的东西是什么?",它将极大地帮助您理解线程安全的含义。

你的代码不访问或改变任何外部变量从它的作用域,所以函数本身是线程安全的,但这并不意味着它不能在"线程不安全"的方式使用。幸运的是,TaskTimeSpan在它们所有的方法和属性中都保证了线程安全,所以你不太可能遇到任何线程安全问题。


不管怎么说,你确实有一个竞争条件的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();
        }
    }
}