【问题标题】:QueueUserWorkItem with WaitCallback getting the return value带有 WaitCallback 的 QueueUserWorkItem 获取返回值
【发布时间】:2017-10-16 07:27:58
【问题描述】:

我正在运营一个从各种商店导入产品 Feed 的网站。 这些提要可能非常大,有些高达 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

现在,我想运行这些任务,而不必等待每个任务完成后再开始下一个任务,因此我尝试了here 所述的设置,首先使用测试函数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 在函数TestMultiThread 中总是21?

【问题讨论】:

  • 它被称为“捕获循环变量”,请参阅下面的答案和here (C#)
  • 但这里真正的问题应该是“如何并行处理提要”,好的答案不会涉及 QueueUserWorkItem。
  • @Henk 在解释了为什么使用 QUWI 会产生意想不到的结果后,我确实谈到了一种更合适的方法来处理这种情况。

标签: asp.net multithreading queueuserworkitem


【解决方案1】:

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

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

最后,由于这是在 ASP.NET 上下文中完成的,因此请注意,启动一堆新线程将抢夺 ASP.NET 线程池中可用于处理传入请求的线程池。 Stephen Cleary explains

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

ThreadPool.QueueUserWorkItem 在你的例子中类似于Task.Run 在他的例子中——它创建了一个后台线程。如果您想在 ASP.NET 中进行即发即弃,请考虑使用 HostingEnvironment.QueueBackgroundWorkItemas Cleary suggests。如果您在导入提要后确实需要对提要执行任何操作,请考虑利用asynchronous programming 在同时等待它们之前启动每个导入(我假设您正在调用 API——一种自然的异步操作——因为您正在“从各种商店”导入供稿)。

【讨论】:

  • 谢谢。看起来QueueBackgroundWorkItem 是最快的赢家。不确定If you actually need to do anything with the feeds after you've imported them 是什么意思,我只需要导入提要的函数的返回值(导入的项目数)...QueueBackgroundWorkItem 仍然是我最好的选择还是我应该研究异步编程?再次感谢。
  • 我试图区分“即发即弃”风格的后台工作和您只是试图并行化的正常工作。听起来你应该创建一个任务来从每个商店导入提要,然后await Task.WhenAll(tasks)
猜你喜欢
  • 1970-01-01
  • 2012-10-10
  • 1970-01-01
  • 2014-06-17
  • 2014-11-06
  • 2021-06-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多