当前位置:编程学习 > C#/ASP.NET >>

调用COM组件的计时器问题

我有一个windows服务。作用是使用一个Timer组件周期性检查数据库,然后调用一个COM组件执行一个功能。
COM组件要求单线程执行,代码如下:

Public Class dxt_Service
Public WithEvents SendTimer As System.Timers.Timer '定时发送计时器
        Public Sub New()
            '初始化计时器
            SendTimer = New System.Timers.Timer(3000)
            With SendTimer
                .AutoReset = True
            End With
        End Sub
        Private Sub SendTimer_Elapsed(ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs) Handles SendTimer.Elapsed
            smutex.WaitOne()
            SendTimer.Stop()
            Try
                SendMessage()
            Catch comex As COMException
                Throw comex
            Catch ex As Exception
                Throw ex
            End Try
            SendTimer.Start()
            smutex.ReleaseMutex()
        End Sub

SendMessage就是读取数据库,然后调用COM的方法。可是运行的时候,第一次执行SendMessage没有任何问题,但是第二次执行就会出现一个COMException异常信息为“服务器出现意外情况。 (异常来自 HRESULT:0x80010105 (RPC_E_SERVERFAULT))”,ErrorCode为“-2147417851”不知道是什么原因?
我把Public WithEvents SendTimer As System.Timers.Timer改成Dim t As System.Windows.Forms.Timer程序可以正常使用,但是windows服务不能用Forms.Timer。请问这个要怎么处理呢?
--------------------编程问答-------------------- 没人回答,我修改下问题吧。怎么让System.Timers.Timer象System.Windows.Forms.Timer那样单线程运行? --------------------编程问答-------------------- 你的SendMessage代码没贴出来,可能是里面有些需要关闭的组件或者端口没有关闭,第二次执行的时候就冒错,另外在你设置的周期时间内是不是能确认可以完成调用,而不会导致第一次调用没有结束,第二次调用又过来了造成冲突。 --------------------编程问答-------------------- SendMessage过程如下:因为还要监视COM组件引发的事件,所以不能关闭COM。
        Public Sub SendMessage()
            Dim db As New dxtdbEntities
            Dim waiting = dxt.MessageState.Waiting.ToString
            Dim msgs = From a In db.tabMessage Where a.State = waiting Order By a.BornTime Descending
            If msgs.Any Then
                For Each dxt_msg In msgs
                    If (Now - dxt_msg.BornTime).TotalHours > 3 Then
                        dxt_msg.State = dxt.MessageState.TimeOver.ToString
                    Else
                        dxt_msg.State = MessageState.Posting.ToString
                        Dim empplog As New EmppLog
                        empplog.Msg = <SendMessage>
                                          <msgid><%= dxt_msg.id %></msgid>
                                          <Telephones><%= dxt_msg.Telephones.ToString %></Telephones>
                                          <Message><%= dxt_msg.Message %></Message>
                                      </SendMessage>.ToString
                        empplog.Time = Now
                        db.AddToEmppLog(empplog)
                        Try
                            db.SaveChanges()
                        Catch ex As Exception

                        End Try

                        Open()

                        '发送短信开始
                        Dim msg As New EMPPLib.ShortMessage
                        Dim mobiles As New EMPPLib.Mobiles

                        mobiles.Add(dxt_msg.Telephones)
                        msg.DestMobiles = mobiles
                        msg.content = dxt_msg.Message
                        msg.needStatus = dxt_msg.NeedStatus           '需要状态报告
                        dxt_msg.dxtServerDataReference.Load()
                        msg.ServiceID = dxt_msg.dxtServerData.Account  'ServiceID值
                        msg.srcID = dxt_msg.dxtServerData.Sender
                        msg.SequenceID = dxt_msg.id
                        msg.SendNow = dxt_msg.SendNow
                        If Not dxt_msg.SendNow Then msg.atTime = dxt_msg.AtTime

                        SyncLock emppctl
                            emppctl.needStatus = dxt_msg.NeedStatus '需要状态报告
                               emppctl.submit(msg)
                        End SyncLock

                        mobiles = Nothing
                        msg = Nothing

                        '发送短信结束

                    End If
                Next
             End If
        End Sub

--------------------编程问答-------------------- System.Timers.Timer改成System.Windows.Forms.Timer就不会出错了。真是奇怪。 --------------------编程问答-------------------- http://msdn.microsoft.com/zh-cn/library/system.timers.timer(v=vs.80).aspx

System.Timers.Timer()

Elapsed 事件在 ThreadPool 线程上引发。如果 Elapsed 事件的处理时间比 Interval 长,在另一个 ThreadPool 线程上将会再次引发此事件。因此,事件处理程序应当是可重入的。

在一个线程调用 Stop 方法或将 Enabled 属性设置为 false 的同时,可在另一个线程上运行事件处理方法。这可能导致在计时器停止之后引发 Elapsed 事件。Stop 方法的示例代码演示了一种避免此争用条件的方法。


SendTimer_Elapsed方法执行的线程是从线程池中取出来的,并不是主线程,并且smutex.WaitOne()
是没用的,到时间后会在另外一个线程中继续执行的
--------------------编程问答-------------------- 你好。谢谢你的回答,你给的MSDN的链接打不开。
我的问题就是我的事件处理程序不可重入,又不能使用System.Windows.Forms.Timer要怎么办呢?

引用 5 楼 xingyuebuyu 的回复:
http://msdn.microsoft.com/zh-cn/library/system.timers.timer(v=vs.80).aspx

System.Timers.Timer()

Elapsed 事件在 ThreadPool 线程上引发。如果 Elapsed 事件的处理时间比 Interval 长,在另一个 ThreadPool 线程上将会再次引发此事件。因此,事件处理程序……
--------------------编程问答--------------------
Imports System.Threading

Public Class Form1

    Dim tm As Threading.Timer
    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        '3秒之后启动定时器,并间隔3秒触发。采用的是线程池中的线程
        tm = New Threading.Timer(New TimerCallback(AddressOf work), Nothing, 3000, 3000)

        Console.WriteLine("id :" + Thread.CurrentThread.ManagedThreadId.ToString + "----" + DateTime.Now.ToString("hh:mm:ss"))

    End Sub

    Sub work(ByVal obj As Object)
        Console.WriteLine("Begin -->" + "id :" + Thread.CurrentThread.ManagedThreadId.ToString + "----" + DateTime.Now.ToString("hh:mm:ss"))
        '停止定时器
        tm.Change(Timeout.Infinite, Timeout.Infinite)

        System.Threading.Thread.Sleep(5000)
        Console.WriteLine(My.Computer.Clock.LocalTime.ToString("id :" + Thread.CurrentThread.ManagedThreadId.ToString + "----" + DateTime.Now.ToString("hh:mm:ss")))
        '重新启动
        tm.Change(3000, 3000)
        Console.WriteLine("Stop -->" + "id :" + Thread.CurrentThread.ManagedThreadId.ToString + "----" + DateTime.Now.ToString("hh:mm:ss"))
    End Sub
End Class



http://msdn.microsoft.com/zh-cn/library/system.threading.timer(VS.80).aspx#Y1034

http://msdn.microsoft.com/zh-cn/library/system.threading.timer.aspx#Y1037


使用 TimerCallback 委托指定希望 Timer 执行的方法。 计时器委托在构造计时器时指定,并且不能更改。 此方法不在创建计时器的线程上执行,而是在系统提供的 ThreadPool 线程上执行。 

创建计时器时,可以指定在第一次执行方法之前等待的时间量(截止时间)以及此后的执行期间等待的时间量(时间周期)。 可以使用 Change 方法更改这些值或禁用计时器。 

 说明  
只要在使用 Timer,就必须保留对它的引用。 对于任何托管对象,如果没有对 Timer 的引用,计时器会被垃圾回收。 即使 Timer 仍处在活动状态,也会被回收。 
 

当不再需要计时器时,请使用 Dispose 方法释放计时器持有的资源。 如果希望在计时器被释放时接收到信号,请使用接受 WaitHandle 的 Dispose(WaitHandle) 方法重载。 计时器已被释放后,WaitHandle 便终止。 

由计时器执行的回调方法应该是可重入的,因为它是在 ThreadPool 线程上调用的。 在以下两种情况中,此回调可以同时在两个线程池线程上执行:一是计时器间隔小于执行此回调所需的时间;二是所有线程池线程都在使用,此回调被多次排队。 


System.Threading.Timer 是一个使用回调方法的计时器,而且由线程池线程服务,简单且对资源要求不高。也可以考虑与 Windows 窗体一起使用的 System.Windows.Forms.Timer 和基于服务器计时器功能的 System.Timers.Timer。这些计时器使用事件并具有附加功能。
--------------------编程问答-------------------- --------------------编程问答-------------------- 7楼的方法可行,不过又出现新的错误。

事件类型: 错误
事件来源: .NET Runtime 2.0 Error Reporting
事件种类: 无
事件 ID: 5000
日期: 2011-3-3
事件: 10:45:47
用户: N/A
计算机:
描述:
EventType clr20r3, P1 dxt_servce2.exe, P2 1.0.0.0, P3 4d6effef, P4 dxt_servce2, P5 1.0.0.0, P6 4d6effef, P7 14, P8 1d, P9 pszqoadhx1u5zahbhohghldgiy4qixhx, P10 NIL.

有关更多信息,请参阅在 http://go.microsoft.com/fwlink/events.asp 的帮助和支持中心。
数据:
0000: 63 00 6c 00 72 00 32 00   c.l.r.2.
0008: 30 00 72 00 33 00 2c 00   0.r.3.,.
0010: 20 00 64 00 78 00 74 00    .d.x.t.
0018: 5f 00 73 00 65 00 72 00   _.s.e.r.
0020: 76 00 63 00 65 00 32 00   v.c.e.2.
0028: 2e 00 65 00 78 00 65 00   ..e.x.e.
0030: 2c 00 20 00 31 00 2e 00   ,. .1...
0038: 30 00 2e 00 30 00 2e 00   0...0...
0040: 30 00 2c 00 20 00 34 00   0.,. .4.
0048: 64 00 36 00 65 00 66 00   d.6.e.f.
0050: 66 00 65 00 66 00 2c 00   f.e.f.,.
0058: 20 00 64 00 78 00 74 00    .d.x.t.
0060: 5f 00 73 00 65 00 72 00   _.s.e.r.
0068: 76 00 63 00 65 00 32 00   v.c.e.2.
0070: 2c 00 20 00 31 00 2e 00   ,. .1...
0078: 30 00 2e 00 30 00 2e 00   0...0...
0080: 30 00 2c 00 20 00 34 00   0.,. .4.
0088: 64 00 36 00 65 00 66 00   d.6.e.f.
0090: 66 00 65 00 66 00 2c 00   f.e.f.,.
0098: 20 00 31 00 34 00 2c 00    .1.4.,.
00a0: 20 00 31 00 64 00 2c 00    .1.d.,.
00a8: 20 00 70 00 73 00 7a 00    .p.s.z.
00b0: 71 00 6f 00 61 00 64 00   q.o.a.d.
00b8: 68 00 78 00 31 00 75 00   h.x.1.u.
00c0: 35 00 7a 00 61 00 68 00   5.z.a.h.
00c8: 62 00 68 00 6f 00 68 00   b.h.o.h.
00d0: 67 00 68 00 6c 00 64 00   g.h.l.d.
00d8: 67 00 69 00 79 00 34 00   g.i.y.4.
00e0: 71 00 69 00 78 00 68 00   q.i.x.h.
00e8: 78 00 20 00 4e 00 49 00   x. .N.I.
00f0: 4c 00 0d 00 0a 00         L.....  
--------------------编程问答-------------------- http://social.microsoft.com/Forums/it-IT/267/thread/0c507a88-c893-40c6-95dd-355b38ebfd77

http://www.eventid.net/display.asp?eventid=5000&eventno=7334&source=.NET%20Runtime%202.0%20Error%20Reporting&phase=1

http://tech.www.com.cn/read.php?tid-5513-page-e.html

http://bytes.com/topic/c-sharp/answers/599247-net-2-0-runtime-error-event-id-5000-a

http://blog.sina.com.cn/s/blog_53864cba01000d6d.html

进程产生未捕获异常。造成进程异常终止


Save it in the same directory as your app and call it something like
myApp.exe.config.

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

<runtime>

<legacyUnhandledExceptionPolicy enabled="true" />

</runtime>

</configuration>
--------------------编程问答-------------------- 最终发现还是老问题没有解决。只是VS无法扑捉线程内的异常了。 --------------------编程问答--------------------
Public Class Form1
    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Try
            Dim td As New Threading.Thread(AddressOf work)
            td.Start()
        Catch ex As Exception
            MsgBox("form----" + ex.ToString)
        End Try

    End Sub

    Sub work()
        Try
            Throw New Exception("hello")
        Catch ex As Exception
            MsgBox("thread ----" + ex.ToString)
        End Try

    End Sub
End Class


线程里的异常要在该线程的执行函数上捕捉


http://blog.sina.com.cn/s/blog_65e729050100m7ms.html

0x80010105:RPC_E_SERVERFAULT
The server threw an exception
 
Very straight forward, the server throws an exception.
 
Repro:DCOM方法中直接抛出一个C++ exception就可以重现这个问题。聪明的你可能会问,为啥米C++ exception不会导致DCOM Server crash呐,那是因为:
 
"Normally, an exception that happens in a DCOM server during execution of a method call is caught by an exception handler in OLE32.DLL, and the method call returns RPC_E_SERVERFAULT."
 
如果要改变这个行为,可以参考IgnoreServerExceptions这个注册表键:

http://support.microsoft.com/kb/198623/en-us


JIT debug a COM local server
Set the following registry key: 
HKEY_LOCAL_MACHINE\Software\Microsoft\Ole
IgnoreServerExceptions="Y"



When the above key is set, the COM/RPC run time will pass the following exceptions on to the caller (for debug purposes): 
STATUS_ACCESS_VIOLATION
STATUS_POSSIBLE_DEADLOCK
STATUS_DATATYPE_MISALIGNMENT
STATUS_INSTRUCTION_MISALIGNMENT
STATUS_ILLEGAL_INSTRUCTION
STATUS_PRIVILEGED_INSTRUCTION --------------------编程问答-------------------- http://blog.csdn.net/hz932/archive/2009/02/08/3868816.aspx

WinForm程序主线程默认是STAThread方式运行的,Windows服务下开的线程默认是MTAThread的方式。

自定义一个线程
Imports System.Threading

Public Class Form1
    Dim tm As Threading.Timer

    Shared manualWork As New AutoResetEvent(False)
    Shared manualElapsed As New AutoResetEvent(False)

    Dim bolExit As Boolean = False

    Private Sub Form1_FormClosed(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosedEventArgs) Handles Me.FormClosed
        bolExit = True
        manualWork.Set()
        manualElapsed.Set()
    End Sub

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load


        Dim td As New Threading.Thread(AddressOf work)
        td.SetApartmentState(ApartmentState.STA)
        td.Start()

        '3秒之后启动定时器,并间隔3秒触发。采用的是线程池中的线程
        tm = New Threading.Timer(New TimerCallback(AddressOf Elapsed), Nothing, 3000, 3000)

        Console.WriteLine("main id :" + Thread.CurrentThread.ManagedThreadId.ToString + "----" + DateTime.Now.ToString("hh:mm:ss"))

    End Sub

    Sub work()
        Dim bolResult As Boolean = False
        While bolExit = False
            bolResult = manualWork.WaitOne()
            ''do something
            Thread.Sleep(5000)
            Console.WriteLine("work id :" + Thread.CurrentThread.ManagedThreadId.ToString + "----" + DateTime.Now.ToString("hh:mm:ss"))

            manualElapsed.Set()
        End While

    End Sub

    Sub Elapsed()
        tm.Change(Timeout.Infinite, Timeout.Infinite)
        manualWork.Set()
        manualElapsed.WaitOne()

        If bolExit = False Then
            tm.Change(3000, 3000)
        Else
            manualWork.Set()
        End If
    End Sub
End Class
--------------------编程问答-------------------- 试过了。还是不行。
程序卡manualElapsed.WaitOne()了。

引用 13 楼 xingyuebuyu 的回复:
http://blog.csdn.net/hz932/archive/2009/02/08/3868816.aspx

WinForm程序主线程默认是STAThread方式运行的,Windows服务下开的线程默认是MTAThread的方式。

自定义一个线程

VB.NET code
Imports System.Threading

Public Class Form1
  ……
补充:.NET技术 ,  VB.NET
CopyRight © 2022 站长资源库 编程知识问答 zzzyk.com All Rights Reserved
部分文章来自网络,