【问题标题】:How can I get a C# timer to execute on the same thread that created it?如何让 C# 计时器在创建它的同一线程上执行?
【发布时间】:2012-12-06 06:22:50
【问题描述】:

我想在必须写入 excel 的 Excel 插件上启用 IMessageFilter。 我以here 为例,上面写着:

消息过滤器是每个线程的,因此我们将此线程注册为 消息过滤器(不是创建加载项的主线程 - 因为那是 Excel 的主线程

我的问题是我的系统在计时器过去时写入 excel,这导致从中断 IMessageFilter 的 ThreadPool 线程调用写入方法,因为 excel 无法访问 IMessageFilterRetryRejectedCall 部分,因为它存在于调用者线程上,而不是由计时器产生的执行线程。

所以,我的问题是:有没有一种方法可以强制定时器的 Elapsed 事件在初始化定时器的同一线程上运行?

编辑:

我的问题是,当IMessageFilter 抛出拒绝/忙碌时,如何让它捕获 excel 错误?

谢谢

【问题讨论】:

  • 我认为这很简单,只需在循环中使用 Thread.Sleep() 和您自己的条件。
  • 谢谢。我想避免以这种方式旋转。

标签: c# .net multithreading timer


【解决方案1】:

您可以使用Timer.SynchronizingObject 属性来编组在间隔已过时发出的事件处理程序调用。

来自MSDN

当 Elapsed 事件由可视 Windows 窗体组件处理时, 比如一个按钮,通过系统线程访问组件 pool 可能会导致异常或可能无法正常工作。避免这种情况 通过将 SynchronizingObject 设置为 Windows 窗体组件来产生效果, 这会导致调用处理 Elapsed 事件的方法 创建组件的同一线程。

假设您使用的是 WinFrom 并且您正在从主窗体中创建计时器实例:

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

【讨论】:

  • 感谢您的回复。我没有使用 winform - 只是一个普通的 C# 类。关于最初的问题,我发现我间接指的是excel my_range["some_name"].copy 而不是 excel_app.ActiveSheet.range["some_name"].copy 的问题。这似乎违反了释放 COM 对象的“两点规则”,如下所述:stackoverflow.com/questions/158706/… ...
  • 您可能想在这里分享您的解决方案
【解决方案2】:

完整答案如下:

问题:将数据写入 excel 的类无法处理来自 excel 的“忙/拒绝”响应消息。

解决方案:按照here 的描述实现IMessageFilter 接口

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我班级的实现部分(见链接):

#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

定义和实现IMessageFilter 接口后,我设置了一个STA Thread 和一个Timers.Timer,如下所示:

线程:

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

计时器:

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

其中StartSTAThread_Handler 定义为:

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

此线程调用我用来写入 Excel 的方法并使用上述IMessageFilter 接口处理被拒绝的消息。我要做的最后一件事是完全限定 excel OM 引用,即;而不是:

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

我必须使用完全限定的引用:

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

虽然这似乎符合我的需求,但它似乎与另一张海报 here 描述的“两点规则”相矛盾...

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-20
    • 2018-07-03
    • 1970-01-01
    • 1970-01-01
    • 2010-12-16
    • 2015-06-13
    相关资源
    最近更新 更多