1

我正在运行一个从各种商店导入产品信息的网站。这些提要可能非常大,有些高达 1GB。目前我通过在循环中调用导入函数来导入这些:

For i As Integer = 0 To dtAllFeeds.Rows.Count - 1

    iImported = ImportFeed(dtAllFeeds(i).id)
    totalProductsImported += iImported
    lblStatus.Text += "FeedId: " + dtAllFeeds(i).id.ToString + "Items Imported: " + iImported.ToString

    If iImported = 0 Then
        MailFunctions.NotifyAdmin("feed error: dtAllFeeds(i).id.ToString)
    End If
Next i

lblStatus.Text += "Total imported: " + totalProductsImported.ToString

这可行,但随着提要的大小或数量增加,处理它们的时间也会增加。所以我不那么优雅地增加了超时时间:

Protected Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Init
    _timeOut = Server.ScriptTimeout
    Server.ScriptTimeout = 36000 '10 hours
End Sub

现在,我想运行这些任务,而不必在开始下一个任务之前等待每个任务完成,所以我尝试了这里描述的设置,首先使用测试功能TestMultiThread

Protected Function TestMultiThread(ByVal Id As Integer, ByVal s As String) As Integer
    LogError("s = " + s)
    For i As Integer = 0 To (Id * 10000)
    Next i
    LogError(Id.ToString + " completed")
    Return Id * 10000
End Function

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim numThreads = 20
Dim toProcess = numThreads
Dim resetEvent = New ManualResetEvent(False)
Dim i As Integer
For i = 1 To numThreads
    ThreadPool.QueueUserWorkItem(New WaitCallback(Sub(state As Object)
                                                      TestMultiThread(i, toProcess.ToString)
                                                      If Interlocked.Decrement(toProcess) = 0 Then
                                                          resetEvent.[Set]()
                                                      End If
                                                  End Sub), Nothing)
Next i

resetEvent.WaitOne()
End Sub

然后我记录了这些错误:

s = 20
s = 20
s = 20
21 completed
21 completed
21 completed
s = 19
s = 18
s = 17
21 completed
21 completed
s = 16
21 completed
s = 15
21 completed
s = 14
21 completed
s = 13
21 completed
s = 12
21 completed
s = 11
21 completed
s = 10
21 completed
s = 9
21 completed
s = 8
21 completed
s = 7
21 completed
s = 6
21 completed
s = 5
21 completed
s = 4
21 completed
s = 3
21 completed
21 completed

我不明白这种记录顺序,为什么我没有看到s = <value>20 个唯一值(但s=20在开始时甚至连续 3 次并且丢失了s=2s=1为什么i总是 21 在函数中TestMultiThread

4

1 回答 1

2

你有几个问题。首先,我什至不能用For i = 0 To (i * 10000)in编译它TestMultiThread,因为你也用i它作为参数名。

其次,您看到的奇怪之处在于您将循环迭代器传递iTestMultiThread,这是一个修改后的闭包——您正在捕获变量本身而不是它的值。到每个线程池委托运行时, 的值为i21,在循环体的每次迭代后递增。要解决此问题,请复制i到循环体内的局部变量,然后将该局部变量传递给TestMultiThread.

最后,由于这是在 ASP.NET 的上下文中完成的,因此请注意,启动一堆新线程将抢夺 ASP.NET 线程池中可用于处理传入请求的线程。斯蒂芬·克利里解释说

  • 该请求开始在 ASP.NET 线程上进行处理。
  • Task.Run在线程池上启动一个任务来进行计算。ASP.NET 线程
    池必须处理(意外地)
    在此请求期间丢失其线程之一。
  • 原始请求线程返回到 ASP.NET 线程池。
  • 计算完成后,该
    线程完成请求并返回到 ASP.NET 线程
    池。ASP.NET 线程池必须处理(意外地)获取另一个线程。

ThreadPool.QueueUserWorkItem在您的情况下,Task.Run与他的示例类似——它创建了一个后台线程。如果您想在 ASP.NET 中进行即发即弃,请HostingEnvironment.QueueBackgroundWorkItem按照Cleary 的建议考虑使用. 如果您在导入提要后确实需要对提要执行任何操作,那么请考虑利用异步编程来启动每个导入,然后再同时等待它们(我假设您正在调用 API——一种自然的异步操作——因为您正在“从各种商店”导入提要)。

于 2017-05-17T06:32:18.877 回答