c# - 是否需要 volatile 关键字来保证对象引用的更改最终被其他线程观察到?
问题描述
背景:我正在开发一个 ASP.NET(经典)WebAPI,它利用从外部 Web 服务检索到的数据集来服务其请求。
- 数据集是不可变的
- 从外部 Web 服务获取数据集的成本相当高(可能需要 5-10 秒)。
- 获取数据集后,需要快速访问数据
- 数据集需要在后台定期更新(替换)。
- 定期更新不得影响读取数据集的代码的性能
- 如果不同的线程看到不可变数据集的版本,这不是问题,但是任何给定的调用都
IDataSetProvider.GetLatestAsync
必须返回最近获取的数据集 - 如果在 AppPool 启动/重新启动时调用
IDataSetProvider.GetLatestAsync
需要一段时间(即尚未获取数据),这不是问题,但是后续调用应该基本上是即时的。
实施总结:
- 一个
DataSetProvider
类,它包含对“最新”数据集值的引用并公开一个接口,用于使用代码来访问最新值。这在 IoC 容器中注册为单实例 - 启动长期运行任务的工作人员,负责更新
DataSetProvider
. 这个工人在 IoC 容器中又是单实例并被自动激活(这里使用 Ninject)
问题:
- 我认为我不需要使用信号量来保护对该
_latestDataSet
字段的访问,因为对 C# 中引用的更新是原子的,并且一次只能由一个线程更新(由于延续可能是不同的线程)。async
我对此是否正确? - 我是否需要关键字
volatile
on_latestDataSet
以确保线程调用IDataSetProvider.GetLatestAsync
获得最新值(并且不会null
因为 JITing 优化或其他原因而永远获得)
实施(为简洁起见省略了不重要的细节):
public interface IDataSetProvider
{
Task<DataSet> GetLatestAsync();
}
public interface IDataSetUpdater
{
void Update(DataSet latestDataSet);
}
public class DataSetProvider : IDataSetProvider, IDataSetUpdater
{
private static readonly TimeSpan InitialFetchTimeout = TimeSpan.FromSeconds(20);
private static readonly TimeSpan InitialDataPollingInterval = TimeSpan.FromMilliseconds(100);
private volatile DataSet _latestDataSet;
public async Task<DataSet> GetLatestAsync()
{
TimeSpan elapsed = TimeSpan.Zero;
// Updated by DataSetProviderWorker task. Only null briefly on application pool startup.
while (_latestDataSet == null)
{
if (elapsed >= InitialFetchTimeout)
{
throw new InvalidOperationException($"Data has not been populated & timeout ({InitialFetchTimeout}) reached.");
}
await Task.Delay(InitialDataPollingInterval);
elapsed += InitialDataPollingInterval;
}
return _latestDataSet;
}
public void Update(DataSet latestDataSet)
{
_latestDataSet = latestDataSet;
}
}
public class DataSetProviderWorker : IStartable
{
private static readonly TimeSpan Period = TimeSpan.FromSeconds(20);
private readonly IDataSetUpdater _dataSetUpdater;
private readonly CancellationTokenSource _cancellationTokenSource;
public void Start()
{
new TaskFactory().StartNew(RunAsync, _cancellationTokenSource.Token, TaskCreationOptions.LongRunning);
}
private async Task RunAsync(object obj)
{
while (!_cancellationTokenSource.IsCancellationRequested)
{
try
{
await DoWorkAsync().ConfigureAwait(false);
}
catch (System.Exception e)
{
_logger.LogError(e, $"{nameof(DataSetProviderWorker)} encountered an unhandled exception in the execution loop. Will retry in {Period}");
}
// Delay execution even if DoWorkAsync threw an exception. Could be a transient error, in which case we don't want to spam
await Task.Delay(Period).ConfigureAwait(false);
}
}
private async Task DoWorkAsync()
{
var immutableData = await _externalDataSource.FetchData();
var latestDataSet = new DataSet(immutableData);
_dataSetUpdater.Update(latestDataSet);
}
public void Stop()
{
_cancellationTokenSource.Cancel();
}
}
解决方案
推荐阅读
- java - Cloud Firestore 获取两个文档 ID 之间的所有文档
- aws-lambda - 通过 API Gateway 对 AWS Lambda 进行 CORS 设置
- python - 理解 Python 的 Callback(ExitStack) 示例
- python - 未处理的拒绝(TypeError):无法读取 react-redux 未定义的属性“查找”
- arrays - RUBY - 如果长度相等,arr.max_by(&:length) 可以返回最后一个实例吗?
- r - R - 如何找到多列的前 10%?
- python - 如何有条件地获取正则表达式中的块
- python - 如何在 Tkinter 中捕获按键事件?
- google-sheets - (谷歌表格/谷歌表格)我希望能够从不同的列中收集实时数据并让它们填充一列
- python - 运行 Python 后端 Firebase