这种扩展方法是否安全?

时间:2016-08-18 15:43:04

标签: c# .net thread-safety task-parallel-library extension-methods

我很难绕着扩展方法缠头。它们是静态类中的静态方法。它们是如何在内部初始化的?例如,我写了下面的扩展方法。它是线程安全吗?

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();
    }
}

1 个答案:

答案 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"?”,它将极大地帮助您了解线程安全的含义。

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

然而,所有这一切,你都有一个与竞争条件有关的错误。如果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();
        }
    }
}