为啥我从后台工作人员调用的委托在主线程中运行

Posted

技术标签:

【中文标题】为啥我从后台工作人员调用的委托在主线程中运行【英文标题】:Why does my delegate called from backgroundworker runs in main thread为什么我从后台工作人员调用的委托在主线程中运行 【发布时间】:2014-12-26 20:03:52 【问题描述】:

我正在编写一个程序,它允许我运行许多不同的硬件测试例程。

这些例程可能非常耗时,持续长达 30 分钟。在此期间,我必须控制一系列测试设备来设置条件并进行测量。

我认为使用后台工作人员来执行任务是理想的,并允许 UI 保持响应。这很有效,直到其中一个例程要求我每 1.5 秒进行一次测量。我正在使用系统计时器来触发这些事件。计时器是在后台工作程序的 doWork 子程序中创建并启动的,但是,我发现委托是在主(UI)线程中运行,而不是在后台工作线程中运行。

我做错了吗?我附上了一个结构相同的简化程序的主要部分。

  Private Sub getMeasurement()
    ' Runs in backgroundWorker thread
    Me.TextBox2.Text = (System.DateTime.Now - startTime).TotalSeconds.ToString
    startTime = System.DateTime.Now
    Debug.Print("Thread name is " & Thread.CurrentThread.Name & ", ID = " & Thread.CurrentThread.ManagedThreadId)
End Sub

 Private Sub OnTimedEvent()
    'Runs in own thread, Calls getMeasurement which runs in BackgroundWorker thread
    Thread.CurrentThread.Name = "OTE"
    Debug.Print("In OnTimedEvent, thread = " & Thread.CurrentThread.Name & ", ID = " & Thread.CurrentThread.ManagedThreadId)
    Dim ServiceTimerDelegate As New ServiceTimerDelegate(AddressOf getMeasurement)
    Me.BeginInvoke(ServiceTimerDelegate)
End Sub

  Private Sub backgroundWorker1_DoWork(ByVal sender As System.Object, _
ByVal e As DoWorkEventArgs) Handles BackgroundWorker1.DoWork

    Thread.CurrentThread.Name = "BW1"
    mTimer = New Timers.Timer(1490) ' 14.9secs (allow for some latency)
    AddHandler mTimer.Elapsed, New Timers.ElapsedEventHandler(AddressOf OnTimedEvent)
    Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)
    Dim i As Integer

    'main timer for measurements every 1.5secs (may change to take interval from UI)
    Select Case e.Argument
        Case "Sunday"

            mTimer.Start()
            Debug.Print("Thread in DoWork = " & Thread.CurrentThread.Name & ", ID = " & Thread.CurrentThread.ManagedThreadId)

            startTime = System.DateTime.Now

            'main loop for temperature ramping
            For i = 20 To 70
                If (worker.CancellationPending = True) Then
                    e.Cancel = True
                Else
                    Thread.Sleep(500)
                    worker.ReportProgress((i - 19) * (100 / 50))
                    i += 1

                End If

            Next
        Case Else
            e.Cancel = True
    End Select
End Sub

【问题讨论】:

是什么让你说 DoWork 事件在 UI 线程中运行? 如果getMeasurement() 正在线程中运行,那么这行:Me.TextBox2.Text = (System.DateTime.Now - startTime).TotalSeconds.ToString 应该会失败,因为您尝试在不是 UI 线程的线程中更新 UI。有没有机会,CheckForIllegalCrossThreadCalls 被禁用了对吧? 这完全正常,您正在使用表单的 BeginInvoke() 方法运行 getMeasurement()。这是必需的,您不能从工作线程更新 UI。 System.Timers.Timer 没有任何用处,您不妨使用 winforms 计时器。 BackgroundWorker 也差不多,它除了睡眠什么都不做。 为了让事情尽可能简单,我似乎引起了混乱。让我试着纠正一下。 我发布的代码不是我的代码,但它确实具有相同的基本结构。它也不完整。我有一个调用 RunWorkerAsync 的 UI,它反过来导致 DoWork。我有与后台工作人员一起工作的测试例程,一切都很好。我通过在 UI 线程中运行的 ReportProgress 子报告进度并更新 UI。例程完成后,后台线程按预期完成。 【参考方案1】:

这里有三件事令人困惑,让我觉得你上面发布的代码实际上并没有使用 BackgroundWorker 的功能。

    DoWork 事件始终在与 UI 不同的线程中运行。这就是它的全部意义所在。 ReportProgress 在 UI 线程中运行,但您没有使用它。

    在上面的代码中,您实际上根本没有运行 BackgroundWorker。相反,您似乎正在使用 ServiceTimerDelegate - 不是 100% 确定我知道那是什么。您已经在 DoWork 处理程序中定义了一个 BackgroundWorker(这没有任何意义),但您似乎从未真正将 BackgroundWorker 连接到该处理程序,也没有在其上调用 RunWorkerAsync。

    即使您在 BackgroundWorker 中运行 getMeasurement(),它也应该会失败 - 您正在更改 UI 项的内容,这会由于 InvalidCrossThreadAccess 而导致异常。如果您需要更改 UI 控件,请使用在 UI 线程中发生的 ReportProgress 事件。

【讨论】:

这不是答案。 @roryap “您没有在 BackgroundWorker 中运行您的代码”并不能解释为什么代码没有按照他在 BackgroundWorker 中的预期方式执行?我的意思是,如果我对这个事实有误,是的,这是一个不好的答案,但这似乎是他困惑的一个重要部分。 @roryap 无论如何,如果您对上述问题有更好的了解,请赐教我们两个,因为我真的很好奇他想要做什么。这是我所拥有的最好的,并且由于他实际上似乎正在运行的唯一东西(ServiceTimerDelegate)似乎是他自己创建的对象这一事实而受到严重损害 - 这个问题是谷歌对其的唯一参考。 请看我在上一个答案中添加的cmets

以上是关于为啥我从后台工作人员调用的委托在主线程中运行的主要内容,如果未能解决你的问题,请参考以下文章

来自dispatch_async全局崩溃的C函数调用,但在主队列上工作

为啥不能在主线程中直接捕获和处理工作线程抛出的异常?

为啥我的 AsyncTask 在主线程上运行?

本机调用阻塞主线程

Handler的工作原理。为啥在子线程中使用Handler会抛出异常

在后台线程上执行异步套接字