阻止从同一用户 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 方法中使用它,利用 LockAsyncIDisposable return 类型的优点来编写 using 语句整齐地包裹关键部分:

using(await userLock.LockAsync(userId))
{
    //user with userId only allowed in this section
    //one at a time.
}

如果我们需要在进入之前等待,它是异步完成的,释放线程来为其他请求提供服务,而不是阻塞直到等待结束,这可能会破坏服务器在负载下的性能。

当然,当您需要扩展到多个网络服务器时,这种方法将不再有效,您需要使用不同的方式(可能通过数据库)进行同步。