【问题标题】:Is this extension method thead safe?这种扩展方法线程安全吗?
【发布时间】:2016-08-19 04:10:58
【问题描述】:

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

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

【问题讨论】:

  • “内部初始化”是什么意思?方法不是“初始化”的东西。

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


【解决方案1】:

所有的扩展方法都是转

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

【讨论】:

  • 感谢您的出色回答以及优化我的代码!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-02
  • 2017-04-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多