/// <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 _); }
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<string>.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); }
/// <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; } }
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(""); } }
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); }
/// <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(); });