正确处理来自不同线程的模块中函数的调用?

时间:2015-06-23 14:03:28

标签: c# .net vb.net multithreading

我目前有一个.NET应用程序,其中一个函数(在一个表单中)调用一个公共函数(位于一个模块中)。表单函数(WriteValueToTag)将一些值传递给模块函数(WriteToPLC),并返回False的True。模块函数(WriteToPLC)始终从WriteValueToTag调用(从不直接调用),仅在表单中使用。但是,表单上有第三方控件,它会引发一个调用WriteValueToTag函数的事件。因此,它在与主UI表单不同的线程上调用。

我遇到的问题是(根据我的日志记录),似乎有时会从不同的线程同时调用此函数。并且参数值显示不同步导致错误。这两个函数都没有更新表单上的任何数据或控件(所以我没有检查Invoke.Required等)我不确定我是否应该检查我的函数或者在自己的线程中启动WriteToPLC函数使用Thread.Start)?我试图在WriteToPLC模块函数的整个内容周围放置一个SyncLock,但这似乎没有帮助。

如何处理模块中的公共函数调用,该模块接收参数,这些参数可以随时从不同的线程调用,以便它为每个调用它的线程运行唯一的?任何示例或链接都会有所帮助。谢谢。

模块中的函数声明如下所示:

Public Function WriteToPLC(ByRef PLC As Logix.Controller, ByRef PLCTag As Logix.Tag) As Boolean

调用它的形式的子程序声明是:

Private Function WriteValueToTag(ByVal PLCTagValue As Object, ByRef PLCTagName As Logix.Tag) As Boolean

...在这里调用WriteToPLC传递适当的值。

我想确保在使用Invoke时保留ByRef参数,所以我不确定我的WriteValueToTag函数的以下代码是否正确:

Delegate Function DelegateWriteValueToTag(ByVal PLCTagValue As Object, ByRef PLCTagName As Logix.Tag)

Private Function WriteValueToTag(ByVal PLCTagValue As Object, ByRef PLCTagName As Logix.Tag) As Boolean
    ''Dim writeLock As New Object

    Dim tagName As Logix.Tag = PLCTagName 'remember PLCTagName since it's a ByRef param

    If (InvokeRequired) Then
        Dim newDelegate As New DelegateWriteValueToTag(AddressOf WriteValueToTag)
        Dim eventArgs() As Object = {PLCTagValue, tagName}
        Log.Debug("Calling Function WriteValueToTag() via InvokeRequired")
        Dim result As Boolean
        result = CType(Invoke(newDelegate, eventArgs), Boolean)
        PLCTagName.Value = tagName.Value 'put value back in since PLCTagName param is passed ByRef
        Return result
    Else
        Try
            Log.Debug("Entering Function WriteValueToTag()")
            Log.Debug("  writing value {0} to tag {1}", PLCTagValue, tagName.Name)

            PLCTagName.Value = PLCTagValue

            Return(WriteToPLC(mPLC, PLCTagName))
        Catch ex As Exception
            HandleError(ex, False)
            Return False
        End Try
    End If
End Function

这是调用WriteValueToTag来保留ByRef参数的正确方法吗?我想知道因为我正在使用“eventArgs()”传递变量,这是一个对象,这将传递对象的引用(即使Invoke的参数声明为ByVal)。如果这是真的,我还需要声明任何局部变量tagName来存储这个值,然后在Invoke之后将其设置回来吗?

还想知道它是否会像这样简单:

 If (InvokeRequired) Then
        Dim newDelegate As New DelegateWriteValueToTag(AddressOf WriteValueToTag)
        Dim eventArgs() As Object = {PLCTagValue, PLCTagName}
        Log.Debug("Calling Function WriteValueToTag() via InvokeRequired")
        Dim result As Boolean
        result = CType(Invoke(newDelegate, eventArgs), Boolean)
        PLCTagName.Value = PLCTagValue 'retain PLCTagName.Value as it's passed ByRef and Invoke will not change it
        Return result

...

1 个答案:

答案 0 :(得分:1)

我不清楚这个问题是如何成为C#问题的。显示的小代码似乎是VB.NET代码。但是,这是你答案的C#版本......

有多种方法可以确保一次只能由一个线程调用库方法。坦率地说,缺少a good, minimal, complete code example能够准确显示您的场景如何运作,不可能确切知道最佳方法是什么。

但是在Windows窗体程序中,最常见的,在某些情况下只是正确的方法是简单地确保使用主UI线程进行所有调用。这是使用Control.Invoke()方法完成的。

如果没有一个好的代码示例,就无法知道代码的实际外观。但基本的想法是修改你的WriteValueToTag()方法,使它看起来像这样:

private void WriteValueToTag(object PLCTagValue, ref Logix.Tag PLCTagName)
{
    Logix.Tag tagName = PLCTagName;

    this.Invoke((MethodInvoker)(() => WriteValueToTagImpl(PLCTagValue, ref tagName)));
    PLCTagName = tagName;
}

private void WriteValueToTagImpl(object PLCTagValue, ref Logix.Tag PLCTagName)
{
    // original method body here
}

你当然可以在一个命名方法中实现它,将“... Impl”方法体放入原始的匿名方法中。要么工作正常。

请注意,如果您将原始方法主体放入匿名方法,则必须在该方法主体中使用tagName而不是PLCTagName,因为您不能使用副引用参数(refout)在匿名方法的主体中(这就是在调用Invoke()之前必须将参数复制到本地的原因,然后本地复制回参数时呼叫返回)。

这里的主要内容是无论谁调用该方法,该方法的实际工作将始终在主UI线程中执行,确保该方法一次只能由一个线程执行,并且它是在主线程中执行的(如果部分或全部困难实际上是“线程关联”问题,而不仅仅是同步问题)。


修改
在回答你关于VB.NET的问题时,没有...我觉得你尝试的代码不会起作用。它肯定在语义上与上述不同,除非传递引用是多余的,否则它将无法实现正确的结果。

另请注意,IMHO不正确且无用,无法使用InvokeRequired属性。它只是使代码多余。相反,只需要总是致电Invoke()。根据您所使用的主题,它会做正确的事情。有关该问题的更多信息,请参阅MSDN’s canonical technique for using Control.Invoke is lame

这是一个VB.NET实现结构与上面的示例相同,但使用您自己的方法体作为起点:

Private Function WriteValueToTag(ByVal PLCTagValue As Object, ByRef PLCTagName As Logix.Tag) As Boolean

    Dim tagName As Logix.Tag = PLCTagName 'remember PLCTagName since it's a ByRef param
    Dim result As Boolean

    Invoke(CType(
    (
        Sub()
            Try
                Log.Debug("Entering Function WriteValueToTag()")
                Log.Debug("  writing value {0} to tag {1}", PLCTagValue, tagName.Name)

                tagName.Value = PLCTagValue
                result = WriteToPLC(mPLC, tagName)
            Catch ex As Exception
                HandleError(ex, False)
                result = False
            End Try
        End Sub
    ), MethodInvoker))

    PLCTagName = tagName
    Return result
End Function

调用Control.Invoke()时可以直接处理by-reference参数。为此,您需要从传递给args方法的原始Invoke()数组中检索更新的值;元素值将根据被调用方法所做的任何修改而更新。但我喜欢按上述方式处理它们。我发现这种语法,其中被调用的委托总是一个无参数的void方法(即Sub()),并且返回值和by-reference参数的处理被放入匿名方法体本身,是更清晰,更容易概括。