/// <summary> /// 反序列化的核心实现,反序列化字符串 /// </summary> /// <param name="str"></param> private void Deserialize(string str) { var keyValue = CoinConfigurationSerializer.Deserialize(str); UpdateMemoryValuesFromExternalValues(KeyValues, OriginalKeyValues, keyValue); OriginalKeyValues = keyValue; }
/// <summary> /// <para/> ```fkv /// <para/> > 凡是 “>” 开头的行都是分隔符,如果后面有内容,将被忽略。 /// <para/> > 于是这就可以写注释用以说明其含义 /// <para/> > 多行的注释只需要打多行 “>” 即可 /// <para/> key0 /// <para/> value0 /// <para/> > /// <para/> key1 /// <para/> ?>value1 /// <para/> ??value1 /// <para/> > key 一定只有一行,在遇到下一个 “>” 之前,都是 value /// <para/> > key/value 存储时,每行一定不会 “>” 开头,如果遇到,则转义为 “?>”,原来的 “?” 转义为 “??” /// <para/> > 转义仅发生在行首。 /// <para/> > 遇到空行,则依然识别为 key,或者 value 的一部分 /// <para/> /// <para/> value /// <para/> /// <para/> > 以上 key 为空字符串,value 为 包含空行的 value(一般禁止写入空字符串作为 key) /// <para/> > 配置文件末尾不包含空行(因为这会识别为 value 的一部分) /// <para/> key0 /// <para/> value9 /// <para/> > 如果存在相同的 key,则处于文件后面的会覆盖文件前面的值。 /// <para/> ``` /// </summary> /// <returns></returns> private async Task Serialize() { // 重写尝试 10 次 Exception?exception = null; const int retryWriteCount = 10; for (var i = 0; i < retryWriteCount; i++) { try { // 如果文件夹不存在,则创建一个新的。 var directory = _file.Directory; if (directory != null && !Directory.Exists(directory.FullName)) { directory.Create(); } try { if (_watcher != null) { await _watcher.StopAsync().ConfigureAwait(false); } // 在每次尝试写入到文件之前都将内存中的键值对序列化一次,避免过多的等待导致写入的数据过旧。 var text = CoinConfigurationSerializer.Serialize(KeyValues); // 将所有的配置写入文件。 using (var fileStream = File.Open(_file.FullName, FileMode.Create, FileAccess.Write)) { using var stream = new StreamWriter(fileStream, Encoding.UTF8); await stream.WriteAsync(text).ConfigureAwait(false); } OriginalKeyValues = new Dictionary <string, string>(KeyValues); } finally { if (_watcher != null) { await _watcher.WatchAsync().ConfigureAwait(false); } } return; } #pragma warning disable CA1031 // Do not catch general exception types catch (Exception ex) #pragma warning restore CA1031 // Do not catch general exception types { // 如果这里吞掉了所有的异常,那么将没有任何途径可以得知为什么存储会失败。 exception = exception ?? ex; Trace.WriteLine(exception); } // 在每次失败重试的时候,都需要等待指定的保存延迟时间。 await Task.Delay(DelaySaveTime).ConfigureAwait(false); } // 记录保存失败时的异常,并抛出。 if (exception != null) { ExceptionDispatchInfo.Capture(exception).Throw(); } }
public FileConfigurationRepo(string fileName) { if (fileName == null) { throw new ArgumentNullException(nameof(fileName)); } var fullPath = Path.GetFullPath(fileName); _file = new FileInfo(fullPath); _saveLoop = new PartialAwaitableRetry(LoopSyncTask); _keyValueSynchronizer = new FileDictionarySynchronizer <string, string>(_file, #pragma warning disable CS8620 // 由于引用类型的可为 null 性差异,实参不能用于形参。 x => CoinConfigurationSerializer.Serialize(x), #pragma warning restore CS8620 // 由于引用类型的可为 null 性差异,实参不能用于形参。 x => CoinConfigurationSerializer.Deserialize(x), // 因为 COIN 格式的序列化器默认会写“文件头”,导致即使是构造函数也会和原始文件内容不同,于是会写入文件,导致写入次数比预期多一些。 // 所以,比较差异时使用 KeyValueEquals 而不是 WholeTextEquals,这可以在目前对注释不敏感的时候提升一些性能。 FileEqualsComparison.KeyValueEquals); // 监视文件改变。 _watcher = new FileWatcher(_file); _currentReadingFileTask = FastSynchronizeAsync(); _watcher.Changed += OnFileChanged; _ = _watcher.WatchAsync(); }