/
CacheDependency.cs
278 lines (243 loc) · 8.41 KB
/
CacheDependency.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace XMS.Core.Caching
{
/// <summary>
/// 在存储于本地缓存对象中的项与文件、缓存键、文件或缓存键的数组或另一个 CacheDependency 对象之间建立依附性关系。
/// CacheDependency 类监视依附性关系,以便在任何这些对象更改时,该缓存项都会自动移除。
/// </summary>
/// <remarks>
/// CacheDependency类提供两种方式以判断文件是否发生变化:
/// HasChanged 属性,通过主动获取该属性,业务逻辑可直接判断文件自上次加载后是否发生变化;
/// 事件通知机制
/// 一旦文件发生变化,FileWatcher 对象就会从监视列表中移除,并不再监测其关联的文件后续发生的任何变化,也就无法接收到任何与该文件关联的事件变化通知;
/// 可以通过以下方式继续监视文件的变化:
/// 当发现或监听到 FileWatcher 关联的文件已经发生变化后,将业务相关的 FileWatcher 设为 null, 然后在需要的时候重新通过 FileWatcher.Get 方法获取
/// 最新的与指定文件关联的 FileWatcher 对象,该对象的 HasChanged 属性为 false
/// 详细示例请参考 缓存服务和配置服务 中通过本类监测关联文件是否发生变化的示例和用法。
/// </remarks>
public class CacheDependency : IDisposable
{
private static Dictionary<string, CacheDependency> dependencies = new Dictionary<string, CacheDependency>(StringComparer.InvariantCultureIgnoreCase);
/// <summary>
/// 使用指定的文件名或目录获取一个依赖项,如果与指定的文件名或目录对应的依赖项不存在,那么新建一个与其关联的依赖项并返回它。
/// </summary>
/// <param name="fileOrDirectory">指定的文件名或目录。</param>
/// <returns>CacheDependency 对象。</returns>
/// <remarks>
/// 如果传入的文件名或目录不合法,那么该方法返回 null。
/// </remarks>
public static CacheDependency Get(string fileOrDirectory)
{
if (!String.IsNullOrEmpty(fileOrDirectory))
{
// fileOrDirectory GetFileName GetDirectoryName
// \zhaixd\a\b > b \zhaixd\a
// D:\a\b > b D:\a
// D:\a\b\ > String.Empty D:\a\b
// \zhaixd\a\b.txt > b.txt \zhaixd\a
// D:\a\b.txt > b.txt D:\a
// D:\ > String.Empty null
// a > String.Empty String.Empty
string directoryName = Path.GetDirectoryName(fileOrDirectory);
if (!String.IsNullOrEmpty(directoryName))
{
string fileName = Path.GetFileName(fileOrDirectory);
lock (dependencies)
{
CacheDependency cacheDependency;
if (dependencies.ContainsKey(fileOrDirectory))
{
cacheDependency = dependencies[fileOrDirectory];
if (!cacheDependency.hasChanged)
{
return cacheDependency;
}
}
cacheDependency = new CacheDependency(fileOrDirectory, directoryName, fileName);
dependencies[fileOrDirectory] = cacheDependency;
cacheDependency.fsw.EnableRaisingEvents = true;
return cacheDependency;
}
}
}
return null;
}
private static void Remove(CacheDependency dependency)
{
lock (dependencies)
{
dependencies.Remove(dependency.fileOrDirectory);
}
}
private string fileOrDirectory;
private bool hasChanged = false;
private FileSystemWatcher fsw;
/// <summary>
/// 表示文件变化的事件
/// </summary>
internal event FileSystemEventHandler Changed;
/// <summary>
/// 使用指定的文件名或目录初始化 CacheDependency 类的新实例。
/// </summary>
/// <param name="fileOrDirectory"></param>
/// <param name="directoryName">目录名</param>
/// <param name="fileName">文件名</param>
private CacheDependency(string fileOrDirectory, string directoryName, string fileName)
{
if (String.IsNullOrEmpty(fileOrDirectory))
{
throw new ArgumentException("fileOrDirectory");
}
if (String.IsNullOrEmpty(directoryName))
{
throw new ArgumentException("directoryName");
}
this.fileOrDirectory = fileOrDirectory;
this.fsw = String.IsNullOrEmpty(fileName) ? new FileSystemWatcher(directoryName) : new FileSystemWatcher(directoryName, fileName);
// 初始化 fsw,但不启用它,直到注册了事件才启用它,参见 FileWatcher.Changed 事件的实现
this.fsw.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
this.fsw.IncludeSubdirectories = false;
this.fsw.Changed += new System.IO.FileSystemEventHandler(this.fsw_Changed);
this.fsw.Created += new FileSystemEventHandler(this.fsw_Changed);
this.fsw.Deleted += new FileSystemEventHandler(this.fsw_Changed);
this.fsw.Renamed += new RenamedEventHandler(this.fsw_Changed);
this.fsw.Error += new ErrorEventHandler(fsw_Error);
}
private bool hasWin32Error = false;
private DateTime? win32ErrorTime = null;
void fsw_Error(object sender, ErrorEventArgs e)
{
Exception err = e.GetException();
// 当检测网络路径时,如果网络断开,则会爆 Code 为 64 的 Win32 异常
if (err is System.ComponentModel.Win32Exception)
{
this.hasWin32Error = true;
this.win32ErrorTime = DateTime.Now;
XMS.Core.Container.LogService.Warn(
String.Format("在监测“{0}”时 发生 Win32 错误,错误码为:{1},详细错误信息为:{2}"
,this.fileOrDirectory, ((System.ComponentModel.Win32Exception)err).NativeErrorCode, ((System.ComponentModel.Win32Exception)err).Message)
, Logging.LogCategory.Cache);
}
else // 经查看 FileSystemWatcher 的内部实现,基本上不会抛出其它类型的异常
{
XMS.Core.Container.LogService.Warn(
String.Format("在监测“{0}”时 发生错误,详细错误信息为:{1}"
, this.fileOrDirectory, err.Message)
, Logging.LogCategory.Cache);
}
}
#region 文件变化事件和文件变化监视
/// <summary>
/// 获取一个值,该值指示当前依赖项是否已经发生变化。
/// </summary>
public bool HasChanged
{
get
{
if (this.hasWin32Error)
{
lock (this.fsw)
{
if (this.hasWin32Error)
{
try
{
this.fsw.EnableRaisingEvents = false;
this.fsw.EnableRaisingEvents = true;
this.hasWin32Error = false;
XMS.Core.Container.LogService.Warn(
String.Format("已成功恢复对“{0}”的监测。", this.fileOrDirectory)
, Logging.LogCategory.Cache);
}
catch (Exception err)
{
if (XMS.Core.Container.LogService.IsDebugEnabled)
{
XMS.Core.Container.LogService.Debug(
String.Format("未能恢复对“{0}”的监测,详细错误信息为:{1}",
this.fileOrDirectory, err.Message),
Logging.LogCategory.Cache);
}
}
}
}
}
return this.hasChanged;
}
}
// 文件变化时仅将当前依赖项设置为已变化
private void fsw_Changed(object sender, FileSystemEventArgs e)
{
if (this.fsw != null && !this.hasChanged)
{
lock (this.fsw)
{
if (this.fsw != null && !this.hasChanged)
{
try
{
this.hasChanged = true;
Remove(this);
// 引发目标事件
if (this.Changed != null)
{
this.Changed(this, e);
}
}
catch (Exception err)
{
Container.LogService.Warn(String.Format("在处理缓存依赖文件\"{0}\"变化事件的过程中发生错误", this.fileOrDirectory), Logging.LogCategory.Cache, err);
}
finally
{
this.Dispose();
}
}
}
}
}
#endregion
#region IDisposable interface
private bool disposed = false;
/// <summary>
/// 释放资源。
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// 释放非托管资源。
/// </summary>
/// <param name="disposing"><b>true</b> 同时释放托管和非托管资源; <b>false</b> 只释放非托管资源。</param>
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
if (this.fsw != null)
{
this.fsw.Dispose();
this.fsw = null;
}
this.Changed = null;
}
}
this.disposed = true;
}
/// <summary>
/// 析构函数
/// </summary>
~CacheDependency()
{
Dispose(false);
}
#endregion
}
}