简体   繁体   English

如何让C#计时器在创建它的同一个线程上执行?

[英]How can I get a C# timer to execute on the same thread that created it?

I want to enable IMessageFilter on an Excel Addin I have to write to excel. 我想在Excel Addin上启用IMessageFilter我必须写入excel。 I have taken example from here which says: 我从这里举了例子说:

Message filters are per-thread, so we register this thread as a message filter (not the main thread that the add-in is created on - because that's Excel's main thread 消息过滤器是每个线程,所以我们将此线程注册为消息过滤器(不是创建加载项的主线程 - 因为这是Excel的主线程

My problem is that my system writes to excel when a timer elapses which results in the writing method being called from a ThreadPool thread which breaks IMessageFilter since the excel cannot access the RetryRejectedCall part of IMessageFilter , because it lives on the caller thread rather than the executing one spawned by the timer. 我的问题是,当计时器过去导致从ThreadPool线程调用写入方法时,我的系统会写入excel,这会破坏IMessageFilter因为excel无法访问IMessageFilterRetryRejectedCall部分,因为它存在于调用者线程而不是执行一个由计时器产生的。

So, my question is: is there a way that I can force a timer's Elapsed event to run on the same thread that initialized the timer? 所以,我的问题是:有没有办法可以强制计时器的Elapsed事件在初始化计时器的同一个线程上运行?

EDIT: 编辑:

My Question is, how do I make IMessageFilter catch excel errors when it throws reject/busy? 我的问题是,如何在抛出拒绝/忙碌时使IMessageFilter捕获excel错误?

Thx 谢谢

You can use Timer.SynchronizingObject property to marshal event-handler calls that are issued when an interval has elapsed. 您可以使用Timer.SynchronizingObject属性来Timer.SynchronizingObject在间隔过去时发出的事件处理程序调用。

Here's from MSDN : 这是来自MSDN

When the Elapsed event is handled by a visual Windows Forms component, such as a button, accessing the component through the system-thread pool might result in an exception or just might not work. 当Elapsed事件由可视Windows窗体组件(如按钮)处理时,通过系统线程池访问组件可能会导致异常或者可能无法正常工作。 Avoid this effect by setting SynchronizingObject to a Windows Forms component, which causes the method that handles the Elapsed event to be called on the same thread that the component was created on. 通过将SynchronizingObject设置为Windows窗体组件来避免此影响,这会导致处理Elapsed事件的方法在创建组件的同一线程上调用。

Assuming you are using a WinFrom and you are creating the timer instance from within the main form: 假设您正在使用WinFrom,并且您正在从主窗体中创建计时器实例:

System.Timers.Timer t = new System.Timers.Timer();
t.SynchronizingObject = this;
t.Elapsed += t_Elapsed;
t.Start();

The complete answer is as follows: 完整的答案如下:

Problem: Class that writes data to excel unable to handle 'busy/reject' response messages from excel. 问题:将数据写入excel的类无法处理来自excel的“忙/拒绝”响应消息。

Solution: Implement IMessageFilter interface as described here 解决方案:按照此处所述实现IMessageFilter接口

IMessageFilter Definition (from link): IMessageFilter定义(来自链接):

namespace ExcelAddinMessageFilter
{
        [StructLayout(LayoutKind.Sequential, Pack = 4)]
        public struct INTERFACEINFO
        {
            [MarshalAs(UnmanagedType.IUnknown)]
            public object punk;
            public Guid iid;
            public ushort wMethod;
        }

        [ComImport, ComConversionLoss, InterfaceType((short)1),
        Guid("00000016-0000-0000-C000-000000000046")]
        public interface IMessageFilter
        {
            [PreserveSig, MethodImpl(MethodImplOptions.InternalCall,
                MethodCodeType = MethodCodeType.Runtime)]
            int HandleInComingCall([In] uint dwCallType, [In] IntPtr htaskCaller,
                [In] uint dwTickCount,
                [In, MarshalAs(UnmanagedType.LPArray)] INTERFACEINFO[]
                lpInterfaceInfo);

            [PreserveSig, MethodImpl(MethodImplOptions.InternalCall,
                MethodCodeType = MethodCodeType.Runtime)]
            int RetryRejectedCall([In] IntPtr htaskCallee, [In] uint dwTickCount,
                [In] uint dwRejectType);

            [PreserveSig, MethodImpl(MethodImplOptions.InternalCall,
                MethodCodeType = MethodCodeType.Runtime)]
            int MessagePending([In] IntPtr htaskCallee, [In] uint dwTickCount,
                [In] uint dwPendingType);
        }
    }

IMessageFilter Implementation part of my class (see link): IMessageFilter实现我的类的一部分(参见链接):

#region IMessageFilter Members

        int ExcelAddinMessageFilter.IMessageFilter.
            HandleInComingCall(uint dwCallType, IntPtr htaskCaller, uint dwTickCount, ExcelAddinMessageFilter.INTERFACEINFO[] lpInterfaceInfo)
        {
            // We're the client, so we won't get HandleInComingCall calls.
            return 1;
        }

        int ExcelAddinMessageFilter.IMessageFilter.
        RetryRejectedCall(IntPtr htaskCallee, uint dwTickCount, uint dwRejectType)
        {
            // The client will get RetryRejectedCall calls when the main Excel
            // thread is blocked. We can handle this by attempting to retry
            // the operation. This will continue to fail so long as Excel is 
            // blocked.
            // As an alternative to simply retrying, we could put up
            // a dialog telling the user to close the other dialog (and the
            // new one) in order to continue - or to tell us if they want to
            // abandon this call
            // Expected return values:
            // -1: The call should be canceled. COM then returns RPC_E_CALL_REJECTED from the original method call.
            // Value >= 0 and <100: The call is to be retried immediately.
            // Value >= 100: COM will wait for this many milliseconds and then retry the call.
            return 1;
        }

        int ExcelAddinMessageFilter.IMessageFilter.
            MessagePending(IntPtr htaskCallee, uint dwTickCount, uint dwPendingType)
        {
            return 1;
        }

        #endregion

With the IMessageFilter interface defined and implemented, I setup a STA Thread and a Timers.Timer as follows: 通过定义和实现IMessageFilter接口,我设置了一个STA Thread和一个Timers.Timer ,如下所示:

Thread: 线:

thread = new Thread(WriteToExcel);
thread.SetApartmentState(ApartmentState.STA);

Timer: 计时器:

timer = new System.Timers.Timer();
timer.Interval = 2000;
timer.Elapsed += new ElapsedEventHandler(StartSTAThread_Handler);

where StartSTAThread_Handler is defined as: 其中StartSTAThread_Handler定义为:

void StartSTAThread_Handler(object source, ElapsedEventArgs e)
{
     thread.Start();
     thread.Join();
     thread = null;
}

This thread calls the method I use to write to Excel and handles rejected messages with the IMessageFilter interface described above. 此线程调用我用于写入Excel的方法,并使用上述IMessageFilter接口处理被拒绝的消息。 The last thing I had to do was to fully qualify excel OM references, that is; 我要做的最后一件事就是完全符合excel OM参考资格,即; instead of: 代替:

Excel.Range rng = app.ActiveSheet.Range["range_name"];
rng.Copy(); // ERRROR: message filter's RetryRejectedCall is NOT called

I had to use fully qualified referencing: 我不得不使用完全限定的引用:

app.ActiveSheet.Range["range_name"].Copy // OK: calls RetryRejectedCall when excel dialog etc is showing

While this seems to work for my needs, it does seem to contradict the "two dot rule" described by another poster here ... 尽管这似乎为我的工作需要,它似乎矛盾的另一个海报中描述的“二点法则” 在这里 ......

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM