问题描述:

I am wondering if this class is thread safe

Can I access the Currencies property's getter without performing a lock?

Should I lock my access to the Currencies property within the GetLiveExchangeRates() method?

public class CurrencyManager

{

public static List<CurrencyModel> Currencies { get; private set; }

private static readonly object LockObj = new object();

public CurrencyManager()

{

Currencies = new List<CurrencyModel>();

}

public static void GetLiveExchangeRates()

{

lock (LockObj)

{

Currencies = GetSomeFooLiveDataFromInternet();

}

}

}

EDIT

How would you refactor it?

网友答案:

If you must stick with a static class, I would refactor the class like this:

public class CurrencyManager
{
    private static readonly IEnumerable<CurrencyModel> currencies = Enumerable<CurrencyModel.Empty();
    private static readonly object LockObj = new object();

    public static void RefreshLiveExchangeRates()
    {
        lock (LockObj)
        {
            CurrencyManager.currencies = GetSomeFooLiveDataFromInternet();
        }
    }

    public static IEnumerable<CurrencyModel> GetCurrencies()
    {
        return CurrencyManager.currencies;
    }
}

Renaming the method to something that better describes what is actually happening. When you call it GetLiveExchangeRates, I would expect it to return back the exchange rates rather than void. I'd then delete the constructor all together and create a GetCurrencies() method that returns the collection, or an empty one if the collection is null. It seems like the collection you are exposing, Currencies, should not be exposed publicly as a List as that allows consumers to change it. You haven't explained what the point of the collection is, so I'm making an assumption by trying to infer what is happening through your naming conventions.

If I were to write this, I would probably hide this behind a service instead. Removing the need for the static class. You hold a reference to the exchange rates in your view model/controller/service what-have-you. When you need to refresh them, hit the service again.

Service

public class CurrencyService
{
    public IEnumerable<CurrencyModel> GetLiveExchangeRates()
    {
        return GetSomeFooLiveDataFromInternet();
    }
}

Consumer (viewmodel/controller etc)

public class MyController
{
    private IEnumerable<CurrencyModel> currentRates;

    public MyController()
    {
        // Instance a new service; or provide it through the constructor as a dependency
        var currencyService = new CurrencyService();
        this.currentRates = currencyService.GetLiveExchangeRates();
    }
}

Then your consuming class would use the collection it fetched from the service. If it wants to, it can pass around that collection to other objects that depend on it. When you feel the collection is stale, you re-fetch them from the service. At this point, you probably wouldn't need to do any locking because only the consumer can use the property and can control when it can and will change the property. This lets multiple instances query for the latest exchange rates without having to do a lock and make everyone queue up to receive them.

Ideally, I'd like to see it passed in as a dependency via the constructor, hidden behind an interface, with the rates refreshed at the time they are needed. So instead of fetching the rates in the constructor, I would fetch them lazily when needed. This would let you do the work async (assuming your real implementation is async).

Edit

If you are storing the collection in the static class for caching purposes, you could store the collection in the service, and always return the collection. The only time a new set of exchange rates is returned is when you clear the cache.

public class CurrencyService
{
    private static IEnumerable<CurrencyModel> currencyRates;
    private static object ratesLock = new object();
    public IEnumerable<CurrencyModel> GetLiveExchangeRates()
    {
        if (currencyRates == null)
        {
            lock (ratesLock)
            {
                currencyRates = GetSomeFooLiveDataFromInternet();
            }
        }

        return currencyRates;
    }

    public void ClearRates()
    {
        currencyRates = null;
    }
}

This is more or less an implementation change. Your controller/viewmodel would continue to hit GetLiveExchangeRates(), but it would only fetch them once from your external service. Each time afterwards it would just return the cache. You only pay the locking fee once, then when other objects hit your service concurrently you don't pay the locking fee again.

相关阅读:
Top