【发布时间】:2020-01-22 23:04:30
【问题描述】:
我有这个功能:
async Task RefreshProfileInfo(List<string> listOfPlayers)
// For each player in the listOfPlayers, checks an in-memory cache if we have an entry.
// If we have a cached entry, do nothing.
// If we don't have a cached entry, fetch from backend via an API call.
这个函数调用非常频繁,比如:
await RefreshProfileInfo(playerA, playerB, playerC)
或
await RefreshProfileInfo(playerB, playerC, playerD)
或
await RefreshProfileInfo(playerE, playerF)
理想情况下,如果玩家不相互重叠,则调用不应相互影响(请求 PlayerE 和 PlayerF 不应阻止对 PlayerA、PlayerB、PlayerC 的请求)。但是,如果玩家确实相互重叠,则第二个调用应该等待第一个调用(请求 PlayerB、PlayerC、PlayerD,应该等待 PlayerA、PlayerB、PlayerC 完成)。
但是,如果这不可能,至少我希望所有调用都是连续的。 (我认为它们仍然应该是异步的,因此它们不会阻塞 other 代码的不相关部分)。
目前,发生的情况是每个 RefreshProfileInfo 并行运行,这导致每次都命中后端(本例中为 9 次)。
相反,我想按顺序执行它们,这样只有第一个调用会命中后端,后续调用只会命中缓存。
我应该使用什么数据结构/方法?我无法弄清楚如何将单独的呼叫“连接”到彼此。我一直在玩 Task.WhenAll() 和 SemaphoreSlim,但我不知道如何正确使用它们。
尝试失败
我失败的尝试背后的想法是有一个帮助类,我可以在其中调用一个函数 SequentialRequest(Task),它会按顺序运行以这种方式调用的所有任务。
List<Task> waitingTasks = new List<Task>();
object _lock = new object();
public async Task SequentialRequest(Task func)
{
var waitingTasksCopy = new List<Task>();
lock (_lock)
{
waitingTasksCopy = new List<Task>(waitingTasks);
waitingTasks.Add(func); // Add this task to the waitingTasks (for future SequentialRequests)
}
// Wait for everything before this to finish
if (waitingTasksCopy.Count > 0)
{
await Task.WhenAll(waitingTasksCopy);
}
// Run this task
await func;
}
我认为这会起作用,但“func”要么立即运行(而不是等待早期任务完成),要么根本不运行,这取决于我如何称呼它。
如果我用这个来调用它,它会立即运行:
async Task testTask()
{
await Task.Delay(4000);
}
如果我用这个来调用它,它永远不会运行:
Task testTask = new Task(async () =>
{
await Task.Delay(4000);
});
【问题讨论】:
-
不清楚您是否可以“连接”这些呼叫。一种常见的缓存技术是在更新缓存数据时保存日期/时间。在随后的调用中检查缓存日期/时间。如果它是可以接受的新鲜的,你很高兴,否则它将刷新缓存并返回新的结果。您可能希望修改调用以包含
maximumAge(以毫秒为单位)。0的值将始终强制刷新。 -
@HABO 感谢您的回复。我的问题是处理 nothing 在缓存中并且有十几个调用请求same 玩家信息的情况。在那种情况下,我不想为 same 播放器打后端十几次。
-
一个空缓存应该有一个
null日期/时间。通过将lock添加到缓存中,您可以序列化访问:第一个命中缓存的线程将强制更新,而其他线程则等待。一旦缓存更新并且锁被释放,其他请求应该在短时间内锁定/读取/解锁缓存,假设它们具有合理的maximumAge值。 (您可以为每个玩家设置一个缓存和锁定。) -
在上面的
RefreshProfileInfo上使用await应该已经使其连续。RefreshProfileInfo里面是什么?您是否正在等待先完成后端调用? -
@HABO 我明白了,谢谢。我必须多加考虑,但我可以看到这种方法可能会奏效。我只是真的希望避免锁以避免死锁的可能性。仅就上下文而言,我预计将有数千名并发玩家连接到该服务器。 IlianPinzon 是的,我正在等待后端调用。在不同的时间调用等待;我只是把它们排成一行来写。
标签: c# asynchronous async-await