线程安全更新缓存的参考数据

Vac*_*ano 7 .net c# thread-safety

假设我有几个List属性.像这样的东西:

List<CustomerTypes> CustomerTypes {get; set;}
List<FormatTypes> FormatTypes {get; set;}
List<WidgetTypes> WidgetTypes {get; set}
List<PriceList> PriceList {get; set;}
Run Code Online (Sandbox Code Playgroud)

因为这些值很少更新,所以我在启动时将它们缓存在我的WCF服务中.然后我有一个服务操作,可以调用它来刷新它们.

服务操作将从数据库中查询所有这些内容,如下所示:

// Get the data from the database.
var customerTypes = dbContext.GetCustomerTypes();
var formatTypes = dbContext.GetFormatTypes();
var widgetTypes = dbContext.GetWidgetTypes ();
var priceList = dbContext.GetPriceList ();

// Update the references
CustomerTypes = customerTypes;
FormatTypes = formatTypes;
WidgetTypes = widgetTypes;
PriceList = priceList;
Run Code Online (Sandbox Code Playgroud)

这导致很少有时间这些并非全部同步.但是,它们并非完全线程安全.(呼叫可以访问新的CustomerType和旧的PriceList.)

我怎样才能这样做,以便在更新引用时,对这些列表的任何使用必须等到所有引用都更新后?

小智 0

我认为将一些东西放在一起作为示例会很有趣。此答案基于 Marc Gravell 的答案(此处)的指导。

以下类接受毫秒值并提供一个事件来通知调用者刷新间隔已达到。

  • 它使用Environment.TickCount,它比使用DateTime 对象快几个数量级。

  • 双重检查锁可防止多个线程同时刷新,并受益于避免每次调用上的锁而减少的开销。

  • 使用 Task.Run() 刷新 ThreadPool 上的数据允许调用者不间断地继续使用现有的缓存数据。

    using System;
    using System.Threading.Tasks;
    
    namespace RefreshTest {
        public delegate void RefreshCallback();
    
        public class RefreshInterval {
            private readonly object _syncRoot = new Object();
    
            private readonly long _interval;
            private long _lastRefresh;
            private bool _updating;
    
            public event RefreshCallback RefreshData = () => { };
    
            public RefreshInterval(long interval) {
                _interval = interval;
            }
    
            public void Refresh() {
                if (Environment.TickCount - _lastRefresh < _interval || _updating) {
                    return;
                }
    
                lock (_syncRoot) {
                    if (Environment.TickCount - _lastRefresh < _interval || _updating) {
                        return;
                    }
    
                    _updating = true;
    
                    Task.Run(() => LoadData());
                }
            }
    
            private void LoadData() {
                try {
                    RefreshData();
    
                    _lastRefresh = Environment.TickCount;
                }
                catch (Exception e) {
                    //handle appropriately
                }
                finally {
                    _updating = false;
                }
            }
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

Interlocked 提供了缓存数据的快速、原子替换。

using System.Collections.Generic;

namespace RefreshTest {
    internal static class ContextCache {
        private static readonly RefreshInterval _refresher = new RefreshInterval(60000);
        private static List<int> _customerTypes = new List<int>();

        static ContextCache() {
            _refresher.RefreshData += RefreshData;
        }

        internal static List<int> CustomerTypes {
            get {
                _refresher.Refresh();

                return _customerTypes;
            }
        }

        private static void RefreshData() {
            List<int> customerTypes = new List<int>();  //dbContext.GetCustomerTypes();

            Interlocked.Exchange(ref _customerTypes, customerTypes);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

数百万个并发调用运行约 100 毫秒(不过请运行您自己的测试!):

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;

namespace RefreshTest {
    internal class Program {
        private static void Main(string[] args) {
            Stopwatch watch = new Stopwatch();
            watch.Start();

            List<Task> tasks = new List<Task>();

            for (int i = 0; i < Environment.ProcessorCount; i++) {
                Task task = Task.Run(() => Test());

                tasks.Add(task);
            }

            tasks.ForEach(x => x.Wait());

            Console.WriteLine("Elapsed Milliseconds: {0}", watch.ElapsedMilliseconds);
            Console.ReadKey();
        }

        private static void Test() {
            for (int i = 0; i < 1000000; i++) {
                var a = ContextCache.CustomerTypes;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

希望有帮助。