ASP.net线程安全混乱

时间:2015-12-30 23:52:06

标签: asp.net multithreading

我在ASP.net应用程序中运行的过程很长,我们迫切需要大幅缩短。该过程正在向大量信用卡收费。目前它以每秒约1次充电的速度运行。我们需要这更像是每秒10次。

因此我们决定使用多个同步线程是一种方法。所以我们基本上把这个庞大的订单列表进行处理,将列表分成十个列表,然后生成一个新线程来同时处理十个列表中的每一个。

此过程的另一个复杂因素是,我们需要报告此过程的进度,而不仅是在应用程序的任何会话中报告启动过程的用户会话,而且报告给任何用户。例如,如果我登录并开始此过程,我将看到一个进度条。如果在我启动流程并且它仍在运行之后,另一个用户登录到其他位置并转到同一页面,他们也会看到进度条。

我做了一些研究,并认为我可以使用Application变量来存储报告进度所需的相关信息。客户端在此页面上定期轮询服务器以查看是否有任何线程正在运行,如果有,它会将有关进程进度的各种统计信息返回给客户端。

看起来这种方法不起作用。当前运行的线程数的简单计数器不能按预期工作。似乎所谓的Application对象的线程安全是安全的,因为没有两个线程能够同时访问同一个变量,但是如果两个线程都试图递增一个变量,其中一个将是能够递增它,而另一个不会,而不是排队并依次递增它,第二个线程继续前进。我确信这是我的线索安全无知。

另一个问题是使用Debug.Print或Debug.WriteLine似乎与Application对象的“线程安全”相同。当每个线程启动时,我们使用Debug.WriteLine输出线程的名称和开始时间,并在完成时,我们做同样的事情来写它完成。我们始终看到十个线程启动,四个线程在调试窗口中结束。

我认为我们不需要使用Application.Lock()和Application.Unlock(),但我在每次写入操作之前和之后都尝试过这些调用,但无效 - 结果是两种方式都一样。

我有很多代码,所以我不确定要分享哪些部分,但这里有一些相关部分:

这是我们创建和启动线程的方式:

For Each oBatch As List(Of Guid) In oOrderBatches
    Dim t As New Threading.Thread(Sub() ProcessPaymentBatch(oBatch, clubrunid, oToken.UserID))
    t.IsBackground = True
    t.Start()
Next

这是每个线程启动的子:

Private Sub ProcessPaymentBatch(oBatch As List(Of Guid), clubrunid As String, UserID As Guid)

    ThreadsRunning(clubrunid) += 1

    Try
        Debug.Print("Thread Start")
        For Each oID As Guid In oBatch
            ‘Do a bunch of processing stuff…
        Next
    Finally
        ThreadsRunning(clubrunid) -= 1
        Debug.Print("Thread End")
    End Try

End Sub

最后,这是线程尝试访问的一个应用程序变量的示例,但似乎失败了。

Private Const _THREADSRUNNING As String = "ThreadsRunningThisRun_"
Public Property ThreadsRunning(clubid As String) As Integer
    Get
        Dim sToken As String = _THREADSRUNNING & clubid
        If Application(sToken) Is Nothing Then
            ThreadsRunning(clubid) = 0
        End If
        Return Application(sToken)
    End Get
    Set(ByVal value As Integer)
        Debug.Print(value)
        Dim sToken As String = _THREADSRUNNING & clubid
        Application.Lock()
        Application(sToken) = value
        Application.UnLock()
    End Set
End Property

此属性的Debug输出如下所示:

Thread Start
1
Thread Start
Thread Start
1
1
4
Thread End
5
3
Thread Start
6
3
1
-1
Thread End
-2
-3

我无法理解为什么会有不同数量的“Thread Start”和“Thread End”调试语句,我不明白线程计数如何得到负数。这就是为什么我对Application和Debug对象的线程安全性感到困惑。

非常感谢您对此事的帮助!

1 个答案:

答案 0 :(得分:0)

没关系,我只是个白痴。这个问题与Application或Debug对象没有线程安全无关,问题在于我的方法(正如预期的那样)。

为了澄清,问题在于我们在写入时将全局变量锁定在应用程序对象中,而不是在读取时。然后我们尝试在阅读时锁定,但仍然有同样的问题。我们没有意识到的是,当递增一个值时,您将获得当前值,添加到该值,然后设置新值。需要锁定所有这三个操作,所以它是这样的:

  • 锁定
  • 获取
  • 添加
  • 解锁

我们之前做的是:

  • 锁定
  • 获取
  • 解锁
  • 添加
  • 锁定
  • 解锁

允许多个线程获取然后设置相同的值,这解释了我们在调试窗口中看到的所有奇怪的事情。