在C#中将方法转换为异步的正确方法?
我正在尝试将以下方法(简化示例)转换为异步方法,因为在C#中将方法转换为异步的正确方法?,c#,asynchronous,async-await,C#,Asynchronous,Async Await,我正在尝试将以下方法(简化示例)转换为异步方法,因为cachemissrolver调用在时间上可能很昂贵(数据库查找、网络调用): //同步版本 公共类ThingCache { 私有静态只读对象_lockObj; //…其他东西 公共对象获取(字符串键,Func cachemissrolver) { if(cache.Contains(key)) 返回缓存[键]; 物项; 锁 { if(cache.Contains(key)) 返回缓存[键]; item=cacheMissResolver();
cachemissrolver
调用在时间上可能很昂贵(数据库查找、网络调用):
//同步版本
公共类ThingCache
{
私有静态只读对象_lockObj;
//…其他东西
公共对象获取(字符串键,Func cachemissrolver)
{
if(cache.Contains(key))
返回缓存[键];
物项;
锁
{
if(cache.Contains(key))
返回缓存[键];
item=cacheMissResolver();
cache.Add(key,item);
}
退货项目;
}
}
网上有很多关于使用异步方法的资料,但我发现关于生成异步方法的建议似乎不那么明确。鉴于这是图书馆的一部分,我下面的尝试是否正确
// Asynchronous attempts
public class ThingCache
{
private static readonly SemaphoreSlim _lockObj = new SemaphoreSlim(1);
// ... other stuff
// attempt #1
public async Task<Thing> Get(string key, Func<Thing> cacheMissResolver)
{
if (cache.Contains(key))
return await Task.FromResult(cache[key]);
Thing item;
await _lockObj.WaitAsync();
try
{
if (cache.Contains(key))
return await Task.FromResult(cache[key]);
item = await Task.Run(cacheMissResolver).ConfigureAwait(false);
_cache.Add(key, item);
}
finally
{
_lockObj.Release();
}
return item;
}
// attempt #2
public async Task<Thing> Get(string key, Func<Task<Thing>> cacheMissResolver)
{
if (cache.Contains(key))
return await Task.FromResult(cache[key]);
Thing item;
await _lockObj.WaitAsync();
try
{
if (cache.Contains(key))
return await Task.FromResult(cache[key]);
item = await cacheMissResolver().ConfigureAwait(false);
_cache.Add(key, item);
}
finally
{
_lockObj.Release();
}
return item;
}
}
//异步尝试
公共类ThingCache
{
私有静态只读信号量lim _lockObj=新信号量lim(1);
//…其他东西
//尝试#1
公共异步任务Get(字符串键,Func cachemissrolver)
{
if(cache.Contains(key))
返回wait Task.FromResult(缓存[key]);
物项;
wait_lockObj.WaitAsync();
尝试
{
if(cache.Contains(key))
返回wait Task.FromResult(缓存[key]);
item=等待任务。运行(cacheMissResolver)。配置等待(false);
_cache.Add(key,item);
}
最后
{
_lockObj.Release();
}
退货项目;
}
//尝试#2
公共异步任务Get(字符串键,Func cachemissrolver)
{
if(cache.Contains(key))
返回wait Task.FromResult(缓存[key]);
物项;
wait_lockObj.WaitAsync();
尝试
{
if(cache.Contains(key))
返回wait Task.FromResult(缓存[key]);
item=await cacheMissResolver().ConfigureAwait(false);
_cache.Add(key,item);
}
最后
{
_lockObj.Release();
}
退货项目;
}
}
使用SemaphoreSlim
是在异步方法中替换lock语句的正确方法吗?(我不能在lock语句的正文中等待。)
我应该改为使用Func
类型的cacheMissResolver
参数吗?尽管这会给调用者带来确保解析器func异步的负担(包装在Task.Run
中),但我知道如果需要很长时间,它将被卸载到后台线程中
谢谢
在异步方法中使用SemaphoreSlim替换lock语句是否正确
对
我应该改为使用Func
类型的cacheMissResolver
参数吗
对。它将允许调用者提供固有的异步操作(如IO),而不是使其仅适用于长时间运行的CPU受限工作。(尽管仍然支持CPU受限的工作,但只要让调用方使用任务即可。如果他们想这样做,就自己运行。)
除此之外,请注意让等待Task.FromResult(…)没有意义代码>在任务中包装一个值
只是立即将其展开是没有意义的。在这种情况下直接使用结果,在这种情况下,直接返回缓存的值。你所做的并不是真的错了,它只是不必要的复杂化/混淆代码。 < P>如果你的缓存在内存中(看起来是这样),那么考虑缓存任务而不是结果。如果两个方法请求相同的密钥,则只会发出一个解析请求,这有一个很好的side属性。此外,由于只有缓存被锁定(而不是解析操作),因此可以继续使用简单的锁定
public class ThingCache
{
private static readonly object _lockObj;
public async Task<Thing> GetAsync(string key, Func<Task<Thing>> cacheMissResolver)
{
lock (_lockObj)
{
if (cache.Contains(key))
return cache[key];
var task = cacheMissResolver();
_cache.Add(key, task);
}
}
}
公共类ThingCache
{
私有静态只读对象_lockObj;
公共异步任务GetAsync(字符串键,Func cacheMissResolver)
{
锁
{
if(cache.Contains(key))
返回缓存[键];
var task=cachemissrolver();
_cache.Add(键、任务);
}
}
}
但是,这也会缓存您可能不希望的异常。避免这种情况的一种方法是允许异常任务最初进入缓存,但在发出下一个请求时将其删除:
public class ThingCache
{
private static readonly object _lockObj;
public async Task<Thing> GetAsync(string key, Func<Task<Thing>> cacheMissResolver)
{
lock (_lockObj)
{
if (cache.Contains(key))
{
if (cache[key].Status == TaskStatus.RanToCompletion)
return cache[key];
cache.Remove(key);
}
var task = cacheMissResolver();
_cache.Add(key, task);
}
}
}
公共类ThingCache
{
私有静态只读对象_lockObj;
公共异步任务GetAsync(字符串键,Func cacheMissResolver)
{
锁
{
if(cache.Contains(key))
{
if(缓存[key].Status==TaskStatus.RanToCompletion)
返回缓存[键];
缓存。删除(键);
}
var task=cachemissrolver();
_cache.Add(键、任务);
}
}
}
如果您有另一个进程定期修剪缓存,您可能会认为此额外检查是不必要的。请考虑使用。@Timothy Shields-这看起来很有用!谢谢。@dbc-在真正的代码缓存中是InMemoryCache的一个实例;但它可以是任何合适的结构。我不想让这个例子复杂化。@rob这是非常相关的,因为您同时从多个线程访问数据结构,而大多数线程都不支持这一点。它需要一个专门设计的数据结构来支持它,比如ConcurrentDictionary
@rob抱歉,我把这个问题和另一个问题混淆了。谢谢。我还没有领会在任务中使用等待的含义。FromResult(…)。谢谢。我没有想过缓存任务本身。也为写这么多关于这个主题的文章干杯,发现你的一些文章对学习更多有用!
public class ThingCache
{
private static readonly object _lockObj;
public async Task<Thing> GetAsync(string key, Func<Task<Thing>> cacheMissResolver)
{
lock (_lockObj)
{
if (cache.Contains(key))
{
if (cache[key].Status == TaskStatus.RanToCompletion)
return cache[key];
cache.Remove(key);
}
var task = cacheMissResolver();
_cache.Add(key, task);
}
}
}