重复操作问题,可以用lock吗

myhid 2016-10-10 12:16:09
aspx页面上有一个按钮不知道为什么有时候会执行二次,每次间隔相差几十豪秒,他的执行后台代码是:
public string XX(xx){
...
与远程服务的交互操作
....
}

现在的问题是,执行“与远程服务的交互操作” 时 相隔时间太短,远程服务把二次操作都认为都是合法的,其实第二次的操作是非法的,如果时间间隔大一点就不会有这种问题,远程服务暂时改不了,所以只能我这边来修改,不要让这种情况发生,可以在 与 远程服务的交互操作之前 用 lock(this){与远程服务的交互操作 } 这样吗?

或者有什么其他方法??谢谢。
...全文
420 17 打赏 收藏 转发到动态 举报
写回复
用AI写文章
17 条回复
切换为时间正序
请发表友善的回复…
发表回复
吉普赛的歌 2016-10-11
  • 打赏
  • 举报
回复
引用 13 楼 myhid 的回复:
[quote=引用 12 楼 hanjun0612 的回复:] 一般我都是ajax提交时,将按钮灰掉,等到返回结果后,才复原。
我也是灰掉按钮的,我这个不是用户点击产生重复提交的,可能是手机网络原因导致的,具体原因不详,总之是产生了豪秒级的二次提交。。。[/quote] 改成隐藏了再试下, 再不行就是有问题的了
大鱼> 2016-10-11
  • 打赏
  • 举报
回复
引用 16 楼 slh506 的回复:
感觉你们说的好像很高深的样子,难道不是因为: if (IsPostBack) -----》 非初次加载?
slh506 2016-10-11
  • 打赏
  • 举报
回复
感觉你们说的好像很高深的样子,难道不是因为: if (IsPostBack) -----》 非初次加载?
software_artisan 2016-10-10
  • 打赏
  • 举报
回复
找到重发的根源才是解决问题的根本之道。服务器端一般是不要求防重发的,对于服务器来说,每秒接受上万次调用都是正常的。
闭包客 2016-10-10
  • 打赏
  • 举报
回复
lock(this) 之后再 Thread.Sleep 是可以的。 只是这不是治本的方法。 问题的根源是为什么用户可以提交非法操作。 估计这种问题是 IsPostBack、AutoPostBack 等属性处理不好。
闭包客 2016-10-10
  • 打赏
  • 举报
回复
很多 sql 的书讲到事务隔离,都会拿检查银行余额做例子,虽然一个是 web 服务器,一个是 sql,但道理是一样的。
myhid 2016-10-10
  • 打赏
  • 举报
回复
引用 12 楼 hanjun0612 的回复:
一般我都是ajax提交时,将按钮灰掉,等到返回结果后,才复原。
我也是灰掉按钮的,我这个不是用户点击产生重复提交的,可能是手机网络原因导致的,具体原因不详,总之是产生了豪秒级的二次提交。。。
正怒月神 版主 2016-10-10
  • 打赏
  • 举报
回复
一般我都是ajax提交时,将按钮灰掉,等到返回结果后,才复原。
myhid 2016-10-10
  • 打赏
  • 举报
回复
@sp1234,谢谢你的回复~我消化一下,
闭包客 2016-10-10
  • 打赏
  • 举报
回复
引用 8 楼 myhid 的回复:
我把问题再描述清楚一点: 其实 这个按钮是微信公众号里面一个html的按钮,点击后会用ajax调用我写的服务器代码 public string XX(xx){ ... 读取数据库用户的余额为100 .... if(余额够){ if(与远程服务的交互操作 ){ 扣余额 } } .... } 不知道是什么原因,偶尔微信端那个按钮同一用户会相隔几十豪秒二次ajax请求我的服务器代码,导致第一次的操作还没完成时,也就是还没来得及扣余额时,第二次的读取用户的余额就已经过来了,也读取到了100,所以又执行了第二次的与远程服务的交互操作。。,不知道我描述清楚问题了没有,现在微信端不知道怎么解决,想在服务器端堵住这个,谢谢各位~
远程服务器不能修改的情况下,一定要锁定。 控制锁的粒度,只锁定【当前用户】的余额。
闭包客 2016-10-10
  • 打赏
  • 举报
回复
引用 8 楼 myhid 的回复:
我把问题再描述清楚一点: 其实 这个按钮是微信公众号里面一个html的按钮,点击后会用ajax调用我写的服务器代码 public string XX(xx){ ... 读取数据库用户的余额为100 .... if(余额够){ if(与远程服务的交互操作 ){ 扣余额 } } .... } 不知道是什么原因,偶尔微信端那个按钮同一用户会相隔几十豪秒二次ajax请求我的服务器代码,导致第一次的操作还没完成时,也就是还没来得及扣余额时,第二次的读取用户的余额就已经过来了,也读取到了100,所以又执行了第二次的与远程服务的交互操作。。,不知道我描述清楚问题了没有,现在微信端不知道怎么解决,想在服务器端堵住这个,谢谢各位~
这是典型的事务隔离问题啊。
myhid 2016-10-10
  • 打赏
  • 举报
回复
我把问题再描述清楚一点: 其实 这个按钮是微信公众号里面一个html的按钮,点击后会用ajax调用我写的服务器代码 public string XX(xx){ ... 读取数据库用户的余额为100 .... if(余额够){ if(与远程服务的交互操作 ){ 扣余额 } } .... } 不知道是什么原因,偶尔微信端那个按钮同一用户会相隔几十豪秒二次ajax请求我的服务器代码,导致第一次的操作还没完成时,也就是还没来得及扣余额时,第二次的读取用户的余额就已经过来了,也读取到了100,所以又执行了第二次的与远程服务的交互操作。。,不知道我描述清楚问题了没有,现在微信端不知道怎么解决,想在服务器端堵住这个,谢谢各位~
  • 打赏
  • 举报
回复
嗯,上面 demo 代码修改一下更准确一下,lock 作用域中只需要包括“读和写关键值”的2条代码,
using System;
using System.Web.UI;

public partial class _Default : System.Web.UI.Page
{
    private static object flag = new object();

    protected void Page_Load(object sender, EventArgs e)
    {
        if (IsPostBack)
        {
            var key = string.Format("交易号_{0} & 明细行号_{1} 已经存在", this.TextBox1.Text, this.TextBox2.Text);
            object r;
            lock (flag)
            {
                r = Cache[key];
                if (r == null)
                    Cache.Insert(key, true, null, DateTime.Now.AddSeconds(5), System.Web.Caching.Cache.NoSlidingExpiration);
            }
            if (r == null)
                ScriptManager.RegisterStartupScript(this, this.GetType(), "判断重复提交", "alert('没有重复提交!');", true);
            else
                ScriptManager.RegisterStartupScript(this, this.GetType(), "判断重复提交", "alert('重复提交了!');", true);
        }
    }
}
你在问题中所写的 lock(this) 语句,首先对于并发的2个页面提交而言,其 this 完全不同,因此不会起任何作用;另外就算是起作用,也丝毫不能说明这样的lock 语句怎么就会极大延迟调用“远程服务交互操作”的问题。不明确的说法就不能作为编程依据。例如你说要整一个 Sleep(n) 语句,那么这个n 应该是多少才能明确保证逻辑上没问题呢?——你只敢于给一个“大得离谱”的时间才能说是明确的,否则时间稍短(但是不明确短到多少)就会出问题。这种思路,就是一个失败者的设计。而一个成功者的设计,总是从原理上彻底解决了问题,而不走这种“尝试修改 Sleep 参数”的思路。
  • 打赏
  • 举报
回复
我要强调的是,即使你的前端页面在浏览器端没有什么修改,你的数据结构设计、数据库设计、服务器端代码,也应该做到“防重复提交”。 不要把这个任务仅仅推卸给前端脚本。
  • 打赏
  • 举报
回复
所谓“远程服务”没有幂等性,这本来就是它设计的一个问题。这种情况在你的页面也存在。 要保证逻辑正确,不仅仅靠前端(浏览器端程序),而正主要地是靠服务器端编程设计。换句话说,就算是 javascript 没有任何有关的针对性的“修改”,你的服务器端也应该针对此 BUG 而校验错误发生情况,并且避免重复提交。 最低级最慢地做法,就是使用关系数据库的“主键”来校验重复 Insert 数据的情况。这是最低级的啦。 我随便给你举一个 asp.net 的例子。假设一个页面只有两个这样两个 Text 和一个 Button,如下
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title></title>
</head>
<body>
    <form id="form1" runat="server">

        <asp:TextBox ID="TextBox1" runat="server" PlaceHolder="交易号" Text="29239"></asp:TextBox>
        <asp:TextBox ID="TextBox2" runat="server" PlaceHolder="明细行号" Text="1"></asp:TextBox>
        <asp:Button ID="Button1" runat="server" Text="Button" />
    </form>
</body>
</html>
要防止“相同交易号相同明细行号”的记录被重复提交,服务器需要校验此情况发生。例如可以“数据缓存”机制保存主键值的5秒钟的一个“快照”,然后每一次提交页面是都判断快照中是否有重复数据。例如:
using System;
using System.Web.UI;

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (IsPostBack)
        {
            var key = string.Format("交易号_{0} & 明细行号_{1} 已经存在", this.TextBox1.Text, this.TextBox2.Text);
            var r = Cache[key];
            if (r == null)
            {
                Cache.Insert(key, true, null, DateTime.Now.AddSeconds(5), System.Web.Caching.Cache.NoSlidingExpiration);
                ScriptManager.RegisterStartupScript(this, this.GetType(), "判断重复提交", "alert('没有重复提交!');", true);
            }
            else
                ScriptManager.RegisterStartupScript(this, this.GetType(), "判断重复提交", "alert('重复提交了!');", true);
        }
    }
}
你可以测试一下,程序根据5秒钟内是否有重复点button的情况而给出不同提示! 而你所说的 lock 是用在防止“刚好”极端并发的情况,也就是说使用了它就能更好地“确保”不会出现重复点击问题。此时可以这样写
using System;
using System.Web.UI;

public partial class _Default : System.Web.UI.Page
{
    private static object flag = new object();

    protected void Page_Load(object sender, EventArgs e)
    {
        if (IsPostBack)
        {
            var key = string.Format("交易号_{0} & 明细行号_{1} 已经存在", this.TextBox1.Text, this.TextBox2.Text);
            object r;
            lock (flag)
            {
                r = Cache[key];
            }
            if (r == null)
            {
                Cache.Insert(key, true, null, DateTime.Now.AddSeconds(5), System.Web.Caching.Cache.NoSlidingExpiration);
                ScriptManager.RegisterStartupScript(this, this.GetType(), "判断重复提交", "alert('没有重复提交!');", true);
            }
            else
                ScriptManager.RegisterStartupScript(this, this.GetType(), "判断重复提交", "alert('重复提交了!');", true);
        }
    }
}
请仔细对比两段代码!只有一条语句需要lock,其它语句不需要lock。如果lock区域过大,包括了完全不需要lock的代码,会眼中影响程序性能。 因此 lock 虽然是“线程同步”的机制,它也是双刃剑。你明白lock为何只需要针对这样一条语句而操作,才应该使用 lock。
  • 打赏
  • 举报
回复
你可以用js将按钮设置为disabled,因为你的aspx页面回调后会刷新页面,所以也不用再取消disabled样式(因为回发的页面上本来就没这个样式)
  • 打赏
  • 举报
回复
lock 并不会改变“执行两次”的情况,只不过是避免不同线程并发进入执行同一块代码区域。 所谓“远程服务”没有幂等性,是它的一个设计问题。如果你要保证有幂等性,你就要准确识别“第二次操作”。无论从客户端还是服务器端,都要通过你的程序来存储和识别是否是重复执行了。 假设用户修改了购物车 GridView 某行信息并且提交了两遍(且不管前端是如何才能提交两遍的),如果你的购物车的“明细Item”有一个“行号”字段,你的“购物车编号+Item行号”作为主键,不关你是通过内存中的数据校验还是通过数据库的校验,你都可以察觉到重复提交情况。 而 lock,仅需要在极短的时间,也就是将数据暂存在内存的时间(多说只有几毫秒的瞬间)才需要lock,之后处理数据(例如与数据库交互、与远程服务交互)时并不需要lock操作。

62,046

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术交流专区
javascript云原生 企业社区
社区管理员
  • ASP.NET
  • .Net开发者社区
  • R小R
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

.NET 社区是一个围绕开源 .NET 的开放、热情、创新、包容的技术社区。社区致力于为广大 .NET 爱好者提供一个良好的知识共享、协同互助的 .NET 技术交流环境。我们尊重不同意见,支持健康理性的辩论和互动,反对歧视和攻击。

希望和大家一起共同营造一个活跃、友好的社区氛围。

试试用AI创作助手写篇文章吧