/// <summary>
        /// 将为指定的 Key 清除。
        /// </summary>
        /// <param name="key">指定项的 Key。</param>
        protected override async Task RemoveValueCoreAsync(string key)
        {
            await _currentReadingFileTask.ConfigureAwait(false);

            CT.Log($"CLEAN {key} = null", _file.Name);
            _keyValueSynchronizer.Dictionary.TryRemove(key, out _);
        }
示例#2
0
        private void WriteAllText(string text)
        {
            try
            {
                _isInWritingFileRegion = true;
                _hasCheckedFileChange  = true;

                DoIOActionWithRetry(i =>
                {
                    CT.Log($"正在写入文件(i):{text.Replace("\r\n", "\\n").Replace("\n", "\\n")}", _file.Name, "Sync");
                    using var fileStream = new FileStream(
                              _file.FullName, FileMode.OpenOrCreate,
                              FileAccess.Write, FileShare.None,
                              0x1000, FileOptions.WriteThrough);
                    using var writer    = new StreamWriter(fileStream, new UTF8Encoding(false, false), 0x1000, true);
                    fileStream.Position = 0;
                    writer.Write(text);
                    writer.Flush();
                    fileStream.SetLength(fileStream.Position);
                });
            }
            finally
            {
                _isInWritingFileRegion = false;
            }
        }
        /// <summary>
        /// 为指定的 Key 存储指定的值。
        /// </summary>
        /// <param name="key">指定项的 Key。</param>
        /// <param name="value">要存储的值。</param>
        protected override async Task WriteValueCoreAsync(string key, string value)
        {
            value = value ?? throw new ArgumentNullException(nameof(value));
            value = value.Replace(Environment.NewLine, "\n");
            await _currentReadingFileTask.ConfigureAwait(false);

            CT.Log($"SET {key} = {value}", _file.Name);
            _keyValueSynchronizer.Dictionary[key] = value;
        }
        /// <summary>
        /// 获取指定 Key 的值,如果不存在,需要返回 null。
        /// </summary>
        /// <param name="key">指定项的 Key。</param>
        /// <returns>
        /// 执行项的 Key,如果不存在,则为 null / Task&lt;string&gt;.FromResult(null)"/>。
        /// </returns>
        protected override async Task <string?> ReadValueCoreAsync(string key)
        {
            await _currentReadingFileTask.ConfigureAwait(false);

            var value = _keyValueSynchronizer.Dictionary.TryGetValue(key, out var v) ? v : null;

            CT.Log($"GET {key} = {value ?? "null"}", _file.Name);
            return(value);
        }
        private async Task <OperationResult> LoopSyncTask(PartialRetryContext context)
        {
            context.StepCount = 10;
            CT.Log($"等待同步...", _file.Name);
            _keyValueSynchronizer.Synchronize();
            Interlocked.Increment(ref _syncWaitingCount);
            await Task.Delay(DelaySaveTime).ConfigureAwait(false);

            return(true);
        }
        /// <summary>
        /// 请求将文件与内存模型进行同步。
        /// 在读文件时调用此方法后,请将返回值赋值给 <see cref="_currentReadingFileTask"/> 以便让后续值的读取使用最新值。
        /// 在写入文件时调用此方法,请仅将返回值用于等待或忽视返回值,因为写入文件不影响后续读值。
        /// </summary>
        /// <param name="tryCount">尝试次数。当失败时会尝试重新同步,此值表示算上失败后限制的同步总次数。当设置为 -1 时表示无限次重试。</param>
        /// <returns>可异步等待的对象。</returns>
        private async Task SynchronizeAsync(int tryCount = -1)
        {
            // 在构造方法中执行时,可能为 null;因此需要判空(在构造函数中,不需要等待读取)。
            if (_currentReadingFileTask != null)
            {
                await _currentReadingFileTask.ConfigureAwait(false);
            }

            CT.Log($"申请同步...", _file.Name);
            await _saveLoop.JoinAsync(tryCount);
        }
        /// <summary>
        /// 尝试重新加载此配置文件的外部修改(例如使用其他编辑器或其他客户端修改的部分)。
        /// <para>外部修改会自动同步到此配置中,但此同步不会立刻发生,所以如果你明确知道外部修改了文件后需要立刻重新加载外部修改,才需要调用此方法。</para>
        /// </summary>
        public async Task ReloadExternalChangesAsync()
        {
            CT.Log($"强制重新读取文件...", _file.Name);
            // 如果之前正在读取文件,则等待文件读取完成。
            await _currentReadingFileTask.ConfigureAwait(false);

            // 现在,强制要求重新读取文件。
            _currentReadingFileTask = FastSynchronizeAsync();
            // 然后,等待重新读取完成。
            await _currentReadingFileTask.ConfigureAwait(false);
        }
        /// <summary>
        /// 请求将文件与内存模型进行同步。
        /// 当采用不安全的读写文件策略时,有可能发生文件读写冲突;而发生时,会尝试 <paramref name="tryCount"/> 次。
        /// </summary>
        /// <param name="tryCount">
        /// 尝试次数。
        /// 当失败时会尝试重新同步,此值表示算上失败后限制的同步总次数。
        /// -1 表示一直尝试直到成功。
        /// </param>
        /// <returns>可异步等待的对象。</returns>
        public async Task SaveAsync(int tryCount = 10)
        {
            CT.Log($"要求保存...", _file.Name);

            // 执行一次等待以便让代码中大量调用的同步(利用 PartialAwaitableRetry 的机制)共用同一个异步任务,节省资源。
            // 副作用是会慢一拍。
            Interlocked.Increment(ref _syncWaitingCount);
            await Task.Delay(DelaySaveTime).ConfigureAwait(false);

            // 执行同步。
            await SynchronizeAsync(tryCount).ConfigureAwait(false);
        }
示例#9
0
        /// <summary>
        /// 将内存中的键值集合同步到文件中,会根据键值的修改时间来修改文件的修改时间。
        /// 此方法保证跨进程/线程的安全执行。
        /// </summary>
        /// <param name="context">用于合并文件与内存中的键值集合。</param>
        private void SynchronizeCore(ICriticalReadWriteContext <TKey, TValue> context)
        {
            // 获取文件的外部更新时间。
            _file.Refresh();
            var utcNow        = DateTimeOffset.UtcNow;
            var lastWriteTime = _file.Exists ? FixFileTime(_file.LastWriteTimeUtc, utcNow) : utcNow;

            if (_file.Exists && _lastFileExists is false)
            {
                // 如果本此触发同步是因为文件新创建,那么无论此文件是新是旧,都视其为最新。
                // 我们认定,如果文件上次不存在而这次存在,即使文件时间是旧的(例如用户从回收站将其恢复),我们也应该视其为最新。
                lastWriteTime = utcNow;
            }
            DateTimeOffset newLastWriteTime;

            if (SupportsHighResolutionFileTime && lastWriteTime == _fileLastWriteTime && _file.Exists == _lastFileExists)
            {
                // 在支持高精度时间的文件系统上:
                // 自上次同步文件以来,文件从未发生过更改(无需提前打开文件)。
                CT.Log($"准备同步时,发现文件时间未改变 {_fileLastWriteTime.LocalDateTime:O}", _file.Name, "Sync");
                newLastWriteTime = SyncWhenFileHasNotBeenUpdated(context, lastWriteTime);
            }
            else
            {
                // 文件已经发生了更改。
                if (_file.Exists)
                {
                    if (SupportsHighResolutionFileTime)
                    {
                        CT.Log($"准备同步时({FileDriveFormat}),发现文件时间改变 {_fileLastWriteTime.LocalDateTime:O} -> {lastWriteTime.LocalDateTime:O}", _file.Name, "Sync");
                    }
                    else
                    {
                        CT.Log($"准备同步时,发现文件系统({FileDriveFormat ?? "null"})不支持高精度时间,强制完全同步 {lastWriteTime.LocalDateTime:O}", _file.Name, "Sync");
                    }
                }
                else
                {
                    CT.Log($"准备同步时,发现文件不存在", _file.Name, "Sync");
                }
                newLastWriteTime = SyncWhenFileHasBeenUpdated(context, lastWriteTime);
            }
            if (lastWriteTime != newLastWriteTime.UtcDateTime)
            {
                CT.Log($"正在更新文件时间 {lastWriteTime.LocalDateTime:O} -> {newLastWriteTime.LocalDateTime:O}", _file.Name, "Sync");
                _file.LastWriteTimeUtc = newLastWriteTime.UtcDateTime;
            }
            // 重新更新文件的信息,因为前面可能发生了更改。
            _lastFileExists    = _file.Exists;
            _fileLastWriteTime = _file.Exists ? newLastWriteTime : DateTimeOffset.MinValue;
        }
        private async Task HandleFileChangedAsync()
        {
            if (_keyValueSynchronizer.DangerousCheckIfThisFileChangeIsFromSelf())
            {
                CT.Log($"忽略本进程导致的文件改变...", _file.Name);
                return;
            }
            else
            {
                CT.Log($"检测到文件被改变...", _file.Name);
            }

            var isPending = _isPendingReload;

            if (isPending)
            {
                // 如果发现已经在准备读取文件了,那么就告诉他又进来了一次,他可能还需要读。
                _isPendingReloadReentered = true;
                return;
            }

            _isPendingReload = true;

            try
            {
                do
                {
                    _isPendingReloadReentered = false;
                    // 等待时间为预期等待时间的 1/2,因为多数情况下,一次文件的改变会收到两次 Change 事件。
                    // 第一次是文件内容的写入,第二次是文件信息(如最近写入时间)的写入。
                    // Interlocked.Increment(ref _syncWaitingCount);
                    await Task.Delay((int)DelayReadTime.TotalMilliseconds / 2).ConfigureAwait(false);
                } while (_isPendingReloadReentered);

                // 如果之前正在读取文件,则等待文件读取完成。
                await _currentReadingFileTask.ConfigureAwait(false);

                // 现在重新读取。
                // - ~~重新读取文件时不影响对键值对的访问,所以不要求其他地方等待 ReadFromFileTask。~~
                // - 但是,如果正在序列化和保存文件,为了避免写入时覆盖未读取完的键值对,需要等待读取完毕。
                // !特别注意!:外部写完文件后配置立刻读,读不到新值;需要调用 ReloadExternalChangesAsync 方法强制加载外部修改;否则将等待自动更新修改。
                _ = SynchronizeAsync();
            }
            finally
            {
                _isPendingReload = false;
            }
        }
示例#11
0
 private string ReadAllText()
 {
     if (_file.Exists)
     {
         return(DoIOActionWithRetry(i =>
         {
             using var fs = new FileStream(
                       _file.FullName, FileMode.Open,
                       FileAccess.Read, FileShare.Read,
                       0x1000, FileOptions.SequentialScan);
             using var reader = new StreamReader(fs, Encoding.UTF8, true, 0x1000, true);
             var text = reader.ReadToEnd();
             CT.Log($"正在读取文件({i}):{text.Replace("\r\n", "\\n").Replace("\n", "\\n")}", _file.Name, "Sync");
             return text;
         }) ?? "");
     }
     else
     {
         CT.Log($"文件不存在,无需读取...", _file.Name, "Sync");
         return("");
     }
 }
示例#12
0
        private string MergeFileTextAndKeyValueText(
            ICriticalReadWriteContext <TKey, TValue> context,
            DateTimeOffset lastWriteTime,
            string text,
            out DateTimeOffset updatedWriteTime, out bool hasChanged)
        {
            var externalKeyValues = _deserializer(text);
            var timedMerging      = context.MergeExternalKeyValues(externalKeyValues, lastWriteTime);
            var mergedKeyValues   = timedMerging.KeyValues.ToDictionary(x => x.Key, x => x.Value);
            var newText           = _serializer(mergedKeyValues);

            updatedWriteTime = timedMerging.Time;
            if (_fileEqualsComparison == FileEqualsComparison.KeyValueEquals)
            {
                hasChanged = !((ICollection <KeyValuePair <TKey, TValue> >)externalKeyValues).SequenceEqualsIgnoringOrder(mergedKeyValues);
            }
            else
            {
                hasChanged = !string.Equals(text, newText, StringComparison.Ordinal);
            }
            CT.Log($"合并键值集合:从文件 {{ {string.Join(", ", externalKeyValues.Keys)} }} 到新 {{ {string.Join(", ", mergedKeyValues.Keys)} }}", _file.Name, "Sync");
            return(newText);
        }
示例#13
0
 /// <summary>
 /// 将文件与内存模型进行同步。
 /// </summary>
 /// <returns>可异步等待的对象。</returns>
 public void Synchronize()
 {
     Dictionary.UpdateValuesFromExternal(_file, context =>
     {
         // 此处代码是跨进程安全的。
         CT.Log($"正在同步,已进入进程安全区...", _file.Name);
         try
         {
             SynchronizeCore(context);
             return;
         }
         catch (IOException)
         {
             // 可能存在某些旧版本的代码通过非进程安全的方式读写文件。
             Interlocked.Increment(ref _fileSyncingErrorCount);
             throw;
         }
         finally
         {
             CT.Log($"正在同步,已退出进程安全区...", _file.Name);
         }
     });
 }
 /// <summary>
 /// 省去任何中间等待环节,立即开始同步(但为了安全,无法省去进程安全区的等待)。
 /// </summary>
 /// <returns>可等待对象。</returns>
 private Task FastSynchronizeAsync() => Task.Run(() =>
 {
     CT.Log($"立即同步...", _file.Name);
     _keyValueSynchronizer.Synchronize();
 });