阻止从同一用户 ID 到 Web 方法的多个请求 C#
block multiple request from same user id to a web method c#
我有一个网络方法上传事务(ASMX 网络服务),它获取 XML 文件,验证文件并将文件内容存储在 SQL 服务器数据库中。我们注意到某些用户可以同时提交同一个文件两次。所以我们可以在我们的数据库中再次使用相同的代码(我们不能在数据库上使用唯一索引或在数据库级别做任何事情,不要问我为什么)。我以为我可以在用户 ID 字符串上使用锁定语句,但我不知道这是否能解决问题。或者如果我可以使用缓存对象来存储所有用户 ID 请求并检查我们是否有来自同一用户 ID 的 2 个请求,我们将执行第一个请求并使用错误消息阻止第二个请求
所以如果有人有任何想法请帮助
阻塞字符串是不好的。阻止您的网络服务器是不好的。
AsyncLocker
是我写的一个方便的 class 允许锁定任何类型,它可以很好地充当字典中的键。它还需要在进入临界区之前进行异步等待(与锁的正常阻塞行为相反):
public class AsyncLocker<T>
{
private LazyDictionary<T, SemaphoreSlim> semaphoreDictionary =
new LazyDictionary<T, SemaphoreSlim>();
public async Task<IDisposable> LockAsync(T key)
{
var semaphore = semaphoreDictionary.GetOrAdd(key, () => new SemaphoreSlim(1,1));
await semaphore.WaitAsync();
return new ActionDisposable(() => semaphore.Release());
}
}
依赖以下两个helperclasses:
惰性词典:
public class LazyDictionary<TKey,TValue>
{
//here we use Lazy<TValue> as the value in the dictionary
//to guard against the fact the the initializer function
//in ConcurrentDictionary.AddOrGet *can*, under some conditions,
//run more than once per key, with the result of all but one of
//the runs being discarded.
//If this happens, only uninitialized
//Lazy values are discarded. Only the Lazy that actually
//made it into the dictionary is materialized by accessing
//its Value property.
private ConcurrentDictionary<TKey, Lazy<TValue>> dictionary =
new ConcurrentDictionary<TKey, Lazy<TValue>>();
public TValue GetOrAdd(TKey key, Func<TValue> valueGenerator)
{
var lazyValue = dictionary.GetOrAdd(key,
k => new Lazy<TValue>(valueGenerator));
return lazyValue.Value;
}
}
ActionDisposable:
public sealed class ActionDisposable:IDisposable
{
//useful for making arbitrary IDisposable instances
//that perform an Action when Dispose is called
//(after a using block, for instance)
private Action action;
public ActionDisposable(Action action)
{
this.action = action;
}
public void Dispose()
{
var action = this.action;
if(action != null)
{
action();
}
}
}
现在,如果您在某处保留一个静态实例:
static AsyncLocker<string> userLock = new AsyncLocker<string>();
您可以在 async
方法中使用它,利用 LockAsync
的 IDisposable
return 类型的优点来编写 using
语句整齐地包裹关键部分:
using(await userLock.LockAsync(userId))
{
//user with userId only allowed in this section
//one at a time.
}
如果我们需要在进入之前等待,它是异步完成的,释放线程来为其他请求提供服务,而不是阻塞直到等待结束,这可能会破坏服务器在负载下的性能。
当然,当您需要扩展到多个网络服务器时,这种方法将不再有效,您需要使用不同的方式(可能通过数据库)进行同步。
我有一个网络方法上传事务(ASMX 网络服务),它获取 XML 文件,验证文件并将文件内容存储在 SQL 服务器数据库中。我们注意到某些用户可以同时提交同一个文件两次。所以我们可以在我们的数据库中再次使用相同的代码(我们不能在数据库上使用唯一索引或在数据库级别做任何事情,不要问我为什么)。我以为我可以在用户 ID 字符串上使用锁定语句,但我不知道这是否能解决问题。或者如果我可以使用缓存对象来存储所有用户 ID 请求并检查我们是否有来自同一用户 ID 的 2 个请求,我们将执行第一个请求并使用错误消息阻止第二个请求 所以如果有人有任何想法请帮助
阻塞字符串是不好的。阻止您的网络服务器是不好的。
AsyncLocker
是我写的一个方便的 class 允许锁定任何类型,它可以很好地充当字典中的键。它还需要在进入临界区之前进行异步等待(与锁的正常阻塞行为相反):
public class AsyncLocker<T>
{
private LazyDictionary<T, SemaphoreSlim> semaphoreDictionary =
new LazyDictionary<T, SemaphoreSlim>();
public async Task<IDisposable> LockAsync(T key)
{
var semaphore = semaphoreDictionary.GetOrAdd(key, () => new SemaphoreSlim(1,1));
await semaphore.WaitAsync();
return new ActionDisposable(() => semaphore.Release());
}
}
依赖以下两个helperclasses:
惰性词典:
public class LazyDictionary<TKey,TValue>
{
//here we use Lazy<TValue> as the value in the dictionary
//to guard against the fact the the initializer function
//in ConcurrentDictionary.AddOrGet *can*, under some conditions,
//run more than once per key, with the result of all but one of
//the runs being discarded.
//If this happens, only uninitialized
//Lazy values are discarded. Only the Lazy that actually
//made it into the dictionary is materialized by accessing
//its Value property.
private ConcurrentDictionary<TKey, Lazy<TValue>> dictionary =
new ConcurrentDictionary<TKey, Lazy<TValue>>();
public TValue GetOrAdd(TKey key, Func<TValue> valueGenerator)
{
var lazyValue = dictionary.GetOrAdd(key,
k => new Lazy<TValue>(valueGenerator));
return lazyValue.Value;
}
}
ActionDisposable:
public sealed class ActionDisposable:IDisposable
{
//useful for making arbitrary IDisposable instances
//that perform an Action when Dispose is called
//(after a using block, for instance)
private Action action;
public ActionDisposable(Action action)
{
this.action = action;
}
public void Dispose()
{
var action = this.action;
if(action != null)
{
action();
}
}
}
现在,如果您在某处保留一个静态实例:
static AsyncLocker<string> userLock = new AsyncLocker<string>();
您可以在 async
方法中使用它,利用 LockAsync
的 IDisposable
return 类型的优点来编写 using
语句整齐地包裹关键部分:
using(await userLock.LockAsync(userId))
{
//user with userId only allowed in this section
//one at a time.
}
如果我们需要在进入之前等待,它是异步完成的,释放线程来为其他请求提供服务,而不是阻塞直到等待结束,这可能会破坏服务器在负载下的性能。
当然,当您需要扩展到多个网络服务器时,这种方法将不再有效,您需要使用不同的方式(可能通过数据库)进行同步。