我试图弄清楚,已经提出了与我的ImageProcessor库的问题在这里添加项目到缓存中,当在那里我得到间歇性文件访问错误.
System.IO.IOException:进程无法访问文件'D:\ home\site\wwwroot\app_data\cache\0\6\5\f\2\7\065f27fc2c8e843443d210a1e84d1ea28bbab6c4.webp',因为它正被另一个进程使用.
我编写了一个类,用于基于散列网址生成的密钥执行异步锁定,但似乎我在实现中遗漏了一些东西.
我的锁类
public sealed class AsyncDuplicateLock
{
/// <summary>
/// The collection of semaphore slims.
/// </summary>
private static readonly ConcurrentDictionary<object, SemaphoreSlim> SemaphoreSlims
= new ConcurrentDictionary<object, SemaphoreSlim>();
/// <summary>
/// Locks against the given key.
/// </summary>
/// <param name="key">
/// The key that identifies the current object.
/// </param>
/// <returns>
/// The disposable <see cref="Task"/>.
/// </returns>
public IDisposable Lock(object key)
{
DisposableScope releaser = new DisposableScope(
key,
s =>
{
SemaphoreSlim locker; …Run Code Online (Sandbox Code Playgroud) 我创建了一个MemoryCache在下面使用.NET的异步缓存.这是代码
public async Task<T> GetAsync(string key, Func<Task<T>> populator, TimeSpan expire, object parameters)
{
if(parameters != null)
key += JsonConvert.SerializeObject(parameters);
if(!_cache.Contains(key))
{
var data = await populator();
lock(_cache)
{
if(!_cache.Contains(key)) //Check again but locked this time
_cache.Add(key, data, DateTimeOffset.Now.Add(expire));
}
}
return (T)_cache.Get(key);
}
Run Code Online (Sandbox Code Playgroud)
我认为唯一的缺点是我需要在锁外等待,所以populator不是线程安全的,但是因为await不能驻留在锁内,我想这是最好的方法.我错过了任何陷阱吗?
更新:当antoher线程使缓存无效时,Esers应答的版本也是线程安全的
public async Task<T> GetAsync(string key, Func<Task<T>> populator, TimeSpan expire, object parameters)
{
if(parameters != null)
key += JsonConvert.SerializeObject(parameters);
var lazy = new Lazy<Task<T>>(populator, true);
_cache.AddOrGetExisting(key, lazy, DateTimeOffset.Now.Add(expire));
return ((Lazy<Task<T>>) _cache.Get(key)).Value;
}
Run Code Online (Sandbox Code Playgroud)
但它可能会更慢,因为它会创建永远不会执行的Lazy实例,并且它在完全线程安全模式下使用Lazy …
class Laziness
{
static string cmdText = null;
static SqlConnection conn = null;
Lazy<Task<Person>> person =
new Lazy<Task<Person>>(async () =>
{
using (var cmd = new SqlCommand(cmdText, conn))
using (var reader = await cmd.ExecuteReaderAsync())
{
if (await reader.ReadAsync())
{
string firstName = reader["first_name"].ToString();
string lastName = reader["last_name"].ToString();
return new Person(firstName, lastName);
}
}
throw new Exception("Failed to fetch Person");
});
public async Task<Person> FetchPerson()
{
return await person.Value;
}
}
Run Code Online (Sandbox Code Playgroud)
Riccardo Terrell 于 2018 年 6 月出版的《.NET 中的并发》一书说道: …
.NET 4.0的System.Lazy <T>类通过枚举LazyThreadSafetyMode提供三种线程安全模式,我将其概括为:
我想要一个延迟初始化的值,它遵循稍微不同的线程安全规则,即:
只有一个并发线程将尝试创建基础值.成功创建后,所有等待的线程将获得相同的值.如果在创建期间发生未处理的异常,它将在每个等待的线程上重新抛出,但它不会被缓存,后续尝试访问基础值将重新尝试创建并可能成功.
因此,与LazyThreadSafetyMode.ExecutionAndPublication的关键不同之处在于,如果创建时"先行"失败,则可以在以后重新尝试.
是否存在提供这些语义的现有(.NET 4.0)类,还是我必须自己编写?如果我自己滚动是否有一种聪明的方法可以在实现中重用现有的Lazy <T>以避免显式锁定/同步?
注意对于一个用例,假设"创建"可能很昂贵并且容易出现间歇性错误,例如从远程服务器获取大量数据.我不想进行多次并发尝试来获取数据,因为它们可能都会失败或全部成功.但是,如果它们失败了,我希望以后能够重试.
我想使用诸如GetOrAdda之类的东西ConcurrentDictionary作为Web服务的缓存。该词典有异步版本吗?GetOrAdd将使用发出Web请求HttpClient,因此,如果该词典的某个版本中GetOrAdd是异步的,那就太好了。
为了消除混淆,字典的内容将是对Web服务的调用的响应。
ConcurrentDictionary<string, Response> _cache = new ConcurrentDictionary<string, Response>();
var response = _cache.GetOrAdd("id", (x) => { _httpClient.GetAsync(x).GetAwaiter().GetResponse();} )
Run Code Online (Sandbox Code Playgroud)