我们的应用程序管理客户端订阅并将加载的项目保存在内存中。当最后一个客户端与项目断开连接时,在延迟之后,项目将被卸载。
卸载项目是一种异步方法,因为它涉及通过网络进行远程服务器通信。
如果另一个客户端在触发卸载计时器的同时订阅要卸载的项目,则会发生不好的事情。
如何同步?
不允许在UnloadProject
块中调用等待的lock
方法。它被认为是危险的,可能会失败,但实际上我根本不需要异步调用。如果我可以进行lock
同步来序列化所有客户端订阅请求,我很乐意将异步方法称为同步并等待结果。并行执行可能很好,但它不适用于互斥的操作(如共享资源)。
我怀疑这个问题不仅限于我们这里的Timer,而是适用于异步方法需要围绕它们进行同步的任何情况。
这是代码,在我看到同步差距的位置有一个“TODO”注释:
public void ClientSubscribeProject(string projectUrlName)
{
lock (clientSubscriptions)
{
// Initialise or increment counter
if (!clientSubscriptions.ContainsKey(projectUrlName))
{
clientSubscriptions.Add(projectUrlName, 1);
}
else
{
clientSubscriptions[projectUrlName]++;
}
// Abort pending timer
if (unloadTimers.ContainsKey(projectUrlName))
{
unloadTimers[projectUrlName].Change(Timeout.Infinite, Timeout.Infinite);
unloadTimers[projectUrlName].Dispose();
unloadTimers.Remove(projectUrlName);
}
}
}
public void ClientUnsubscribeProject(string projectUrlName)
{
lock (clientSubscriptions)
{
// Decrement counter
clientSubscriptions[projectUrlName]--;
// Clear 0 counter and start timer
if (clientSubscriptions[projectUrlName] == 0)
{
var timer = new Timer(
async (state) =>
{
lock (clientSubscriptions)
{
if (clientSubscriptions.ContainsKey(projectUrlName))
return; // Another client has subscribed in this moment
}
// TODO: <-- Still a synchronisation gap!
await UnloadProject(projectUrlName);
},
null,
300000, // 5 minutes
Timeout.Infinite);
unloadTimers.Add(projectUrlName, timer);
clientSubscriptions.Remove(projectUrlName);
}
}
}