簡體   English   中英

使用Thread運行COM對象時Excel崩潰

[英]Excel Crashing when running COM object with Thread

運行COM(自制)計時器時,我在Excel中遇到錯誤。 基本上,Excel實例化Timer,將其初始化並啟動。 然后,計時器每隔X毫秒滴答一次,引發Excel捕獲的事件(相當標准的東西)。 我沒有將Excel本身用作計時器,因為它的滴答速度不會快於每秒(對於我而言,這太長了)

我的問題是,如果在計時器引發事件時單擊並按住電子表格,Excel崩潰將非常嚴重。 不幸的是,當計時器運行時,用戶需要(有時)單擊電子表格並對其進行修改。

我看到某個地方可以在計時器中使用IMessageFilter接口。 這應該確保如果在事件觸發時Excel很忙,計時器就可以看到並采取相應的措施。 但是我無法正確實現它。

如果有人可以幫助我,那就太好了。

這是我正在使用的源代碼:

在Excel中,我有一個帶有WithEvents ExcelTimer.ExcelTimer對象的單例,這是我的單例的代碼:

Option Explicit
Private Const m_sMODULE_NAME = "cTimerManager"

Public WithEvents oCsharpTimer As ExcelTimer.ExcelTimer

Private Sub Class_Initialize()
    Set oCsharpTimer = New ExcelTimer.ExcelTimer

    'The following two lines are called dynamically from somewhere else 
    'normally but for simplicity of my post I have put them here
    oCsharpTimer.Initialize 500  
    oCsharpTimer.StartTimer
End Sub

Private Sub oCsharpTimer_TimeTickEvt(ByVal o As Variant, ByVal Time As String)
    Const sPROCEDURE_NAME = "oCsharpTimer_TimeTickEvt"
    On Error GoTo ErrorHandler

    '"Send" confirmation with time to the COM object.
    oCsharpTimer.TimeReceived Time

    'Do whatever I wanna do when the event is trigger

CleanUp:
    Exit Sub

ErrorHandler:
    'My Error handling structure
    If ProcessError(m_sMODULE_NAME, sPROCEDURE_NAME, Err) Then
        Stop
        Resume
    Else
        Resume Next
    End If
End Sub

這是我的COM對象的代碼:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Runtime.InteropServices;
using System.Windows.Forms;


namespace ExcelTimer
{
    public delegate void EventTimeRaiser(object o, string Time);

    //COM Interface
    public interface ICOMExcelTimer
    {
        [DispId(1)]        
        void StartTimer();
        [DispId(2)]
        void StopTimer();
        [DispId(3)]
        void Initialize(int TimeInMilliseconds);
        [DispId(4)]
        void TimeReceived(string ReceivedTime);
    }

    //Event interface 
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    public interface ICOMExcelTimerEvent
    {
        [DispId(1000)]
        void TimeTickEvt(object o, string Time);
    }


    [ClassInterface(ClassInterfaceType.None),
    ComSourceInterfaces(typeof(ICOMExcelTimerEvent)),
    ComVisible(true)]
    public class ExcelTimer : ICOMExcelTimer, IMessageFilter
    {
        private event EventTimeRaiser TimeTickEvt;
        private bool _started;
        private bool _initialised;
        private int _timeInMilliseconds;
        private string _lastTimeReceived;
        private Control _control;
        private Thread _timerThread;
        private IAsyncResult _AsynchronousResult;

        [ComVisible(true)]
        public void Initialize(int TimeInMilliSeconds)
        {   
            //To be called by Excel to set which timer parameters it wants
            _timeInMilliseconds = TimeInMilliSeconds;
            _initialised = true;

            //Make sure we clear the last confirmation received 
            //since we are re-initialising the object
            _lastTimeReceived = ""; 
        }


        [ComVisible(true)]
        public void TimeReceived(string ReceivedTime)
        {
            //Store the last time received. Excel calls this function
            _lastTimeReceived = ReceivedTime;
        }

        public ExcelTimer()
        {
            _lastTimeReceived = "";
        }

        [ComVisible(true)]
        //Start the Timer
        public void StartTimer()
        {
            //If the timer has not been initialised just yet
            if (!_initialised)
            {
                //Sends back an error message to Excel
                TimeTickEvt(this, "Error: Timer Not Initialised");
                return;
            }

            try
            {
                //Start the timer
                _timerThread = new Thread(new ThreadStart(TimeTicking));

                //Start the Thread
                _started = true;
                _timerThread.Start();
            }
            catch (Exception ex)
            {
                System.IO.File.AppendAllText(@"C:\ErrorLog.txt", ex.Message + " - StartTimer - " + DateTime.Now.ToString("hh:mm:ss.f") + "\n");
            }
        }


        [ComVisible(true)]
        //Stop the timer
        public void StopTimer()
        {            
            //Stop the Thread
            _timerThread.Abort();
            //Change the status
            _started = false;
        }

        private void TimeTicking()
        {
            string SentTime;

            //As long as the timer is running
            while (_started)
            {
                try
                {
                    //Pause the timer for the right number of milliseconds

                    Thread.Sleep(_timeInMilliseconds);

                    SentTime = DateTime.Now.ToString("hh:mm:ss.ffff");

                    //########### The CODE Errors Here when Excel is busy!  ###########
                    //Raise an event for Excel to grab with the time that the thread finished the sleep at.
                    OnTimeTick(SentTime);

                    //_lastTimeReceived is used so that if the link between Excel and the Thread is broken the thread stops after sometimes
                    //if no confirmation was received from Excel.

                    //If no last time was received just yet, we setup the last time received to the sent time
                    if (_lastTimeReceived.Equals(""))
                    {
                        _lastTimeReceived = SentTime;
                    }
                    //If the last time received is older than 10 x TimeInMilliseconds (in Seconds) we stop the timer.
                    else if (Convert.ToDateTime(_lastTimeReceived).AddSeconds(_timeInMilliseconds * 10 / 1000) < Convert.ToDateTime(SentTime))
                    {
                        OnTimeTick("Timer timed out. No Confirmation for more than " + _timeInMilliseconds * 10 / 1000 + " second(s).");

                        //Stop the timer because the thread has not received a last time recently
                        _started = false;

                    }
                }
                catch (Exception ex)
                {
                    System.IO.File.AppendAllText(@"C:\ErrorLog.txt", ex.Message + " - TimeTicking - " + DateTime.Now.ToString("hh:mm:ss.f") + "\n");
                }

            }
        }

        protected virtual void OnTimeTick(string Time)
        {
            try
            {
                if (Time != null)
                {
                    //Raise the event
                    TimeTickEvt(this, Time);                    
                }
            }
            catch (Exception ex)
            {
                System.IO.File.AppendAllText(@"C:\ErrorLog.txt", ex.Message + " - OnTimeTick - " + DateTime.Now.ToString("hh:mm:ss.f") + "\n");
            }
        }        
    }
}

我無法為您提供C#編碼,但是如果您希望使用Excel和精度超過1秒的計時器,則可以在VBA中完成:

Public Declare Function timeBeginPeriod Lib "winmm.dll" (ByVal uPeriod As Long) As Long

Public Declare Function timeGetTime Lib "winmm.dll" () As Long

然后,如果需要,您可以使用子例程中的timeGetTime函數以毫秒為單位返回時間。

歸還到期款項:

http://www.excelforum.com/excel-programming-vba-macros/738087-using-milliseconds-in-vba.html?p=2748931&viewfull=1#post2748931

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM