首页 > 解决方案 > 是否需要 volatile 关键字来保证对象引用的更改最终被其他线程观察到?

问题描述

背景:我正在开发一个 ASP.NET(经典)WebAPI,它利用从外部 Web 服务检索到的数据集来服务其请求。

实施总结:

问题:

实施(为简洁起见省略了不重要的细节):

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

标签: c#multithreadingthread-safetyvolatile

解决方案


推荐阅读