/// <summary> /// 将为指定的 Key 清除。 /// </summary> /// <param name="key">指定项的 Key。</param> protected override async Task RemoveValueCoreAsync(string key) { await LoadFromFileTask.ConfigureAwait(false); CT.Debug($"{key} = null", "Set"); KeyValues.TryRemove(key, out _); }
/// <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 LoadFromFileTask.ConfigureAwait(false); CT.Debug($"{key} = {value}", "Set"); KeyValues[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 LoadFromFileTask.ConfigureAwait(false); var value = KeyValues.TryGetValue(key, out var v) ? v : null; CT.Debug($"{key} = {value ?? "null"}", "Get"); return(value); }
/// <summary> /// 反序列化文件 /// </summary> /// <param name="file"></param> /// <returns></returns> private async Task DeserializeFile(FileInfo file) { CT.Debug($"读取 {file.FullName}", "File"); if (!File.Exists(file.FullName)) { UpdateMemoryValuesFromExternalValues(KeyValues, OriginalKeyValues, new Dictionary <string, string>()); OriginalKeyValues.Clear(); return; } const int retryCount = 100; for (var i = 0; i < retryCount; i++) { try { // 一次性读取完的性能最好 var str = File.ReadAllText(file.FullName); Deserialize(str); _lastDeserializeTime = DateTimeOffset.Now; return; } catch (IOException) { const int waitTime = 10; // 读取配置文件出现异常,忽略所有异常 await Task.Delay(waitTime).ConfigureAwait(false); // 通过测试发现在我的设备,写入平均时间是 6 毫秒,也就是如果存在多实例写入,也不会是 waitTime*retryCount 毫秒这么久,等待 waitTime*retryCount 毫秒也是可以接受最大的值 } #pragma warning disable CA1031 // Do not catch general exception types catch #pragma warning restore CA1031 // Do not catch general exception types { // 这里的代码因为从一个 IO 线程调进来,所以当调用方使用 await 等待,会使得这里抛出的异常回到 IO 线程,导致应用程序崩溃。 // 这里可能的异常有: // - UnauthorizedAccessException 在文件只读、文件实际上是一个文件夹、没有读权限或者平台不支持时引发。 const int waitTime = 10; await Task.Delay(waitTime).ConfigureAwait(false); } } }
/// <summary> /// 在配置文件改变的时候,重新读取文件。 /// </summary> private async void OnFileChanged(object?sender, EventArgs e) { CT.Debug($"检测到文件被改变...", "File"); var isPending = _isPendingReread; if (isPending) { // 如果发现已经在准备读取文件了,那么就告诉他又进来了一次,他可能还需要读。 _isPendingRereadReentered = true; return; } _isPendingReread = true; try { do { _isPendingRereadReentered = false; // 等待时间为预期等待时间的 1/2,因为多数情况下,一次文件的改变会收到两次 Change 事件。 // 第一次是文件内容的写入,第二次是文件信息(如最近写入时间)的写入。 await Task.Delay((int)DelayReadTime.TotalMilliseconds / 2).ConfigureAwait(false); } while (_isPendingRereadReentered); // 如果之前正在读取文件,则等待文件读取完成。 await LoadFromFileTask.ConfigureAwait(false); // 现在重新读取。 // - ~~重新读取文件时不影响对键值对的访问,所以不要求其他地方等待 LoadFromFileTask。~~ // - 但是,如果正在序列化和保存文件,为了避免写入时覆盖未读取完的键值对,需要等待读取完毕。 // !特别注意!:外部写完文件后配置立刻读,读不到新值;需要调用 ReloadExternalChangesAsync 方法强制加载外部修改;否则将等待自动更新修改。 _ = RequestReloadingFile(); } finally { _isPendingReread = false; } }