Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/305.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 在主UI线程的.NET中引发事件_C#_Multithreading_Design Patterns - Fatal编程技术网

C# 在主UI线程的.NET中引发事件

C# 在主UI线程的.NET中引发事件,c#,multithreading,design-patterns,C#,Multithreading,Design Patterns,我正在.NET中开发一个类库,其他开发人员最终将使用它。该库使用了一些辅助线程,这些线程触发状态事件,这些事件将导致WinForms/WPF应用程序中的某些UI控件更新 通常,对于每次更新,您都需要检查WinForms上的.InvokeRequired属性或等效的WPF属性,并在主UI线程上调用该属性进行更新。这可能会很快变老,让最终开发人员这么做会让人感觉不对劲,所以 我的库是否可以从主UI线程触发/调用事件/委托? 特别是 我是否应该自动“检测”要使用的“主”线程 如果不是,我是否应该要求最

我正在.NET中开发一个类库,其他开发人员最终将使用它。该库使用了一些辅助线程,这些线程触发状态事件,这些事件将导致WinForms/WPF应用程序中的某些UI控件更新

通常,对于每次更新,您都需要检查WinForms上的.InvokeRequired属性或等效的WPF属性,并在主UI线程上调用该属性进行更新。这可能会很快变老,让最终开发人员这么做会让人感觉不对劲,所以

我的库是否可以从主UI线程触发/调用事件/委托?

特别是

  • 我是否应该自动“检测”要使用的“主”线程
  • 如果不是,我是否应该要求最终开发人员在应用程序启动时调用一些(伪)
    UseThisThreadForEvents()
    方法,以便从该调用中获取目标线程

  • 您的库可以检查事件调用列表中每个委托的目标,如果目标是ISynchronizeInvoke,则封送对目标线程的调用:

    private void RaiseEventOnUIThread(Delegate theEvent, object[] args)
    {
      foreach (Delegate d in theEvent.GetInvocationList())
      {
        ISynchronizeInvoke syncer = d.Target as ISynchronizeInvoke;
        if (syncer == null)
        {
          d.DynamicInvoke(args);
        }
        else
        {
          syncer.BeginInvoke(d, args);  // cleanup omitted
        }
      }
    }
    
    另一种使线程契约更加明确的方法是,要求库的客户端为希望您在其上引发事件的线程传入ISynchronizeInvoke或SynchronizationContext。与“秘密检查代理目标”方法相比,这为库的用户提供了更多的可见性和控制


    关于你的第二个问题,我将把线程封送的东西放在你的OnXxx或用户代码调用可能导致引发事件的任何API中。

    你可以使用该类在WinForms或WPF中封送对UI线程的调用。

    你可以将主线程的调度程序存储在你的库中,使用它来检查您是否正在UI线程上运行,并在必要时通过它在UI线程上执行

    提供了一个很好的介绍和示例来说明如何做到这一点

    其要点如下:

    private Dispatcher _uiDispatcher;
    
    // Call from the main thread
    public void UseThisThreadForEvents()
    {
         _uiDispatcher = Dispatcher.CurrentDispatcher;
    }
    
    // Some method of library that may be called on worker thread
    public void MyMethod()
    {
        if (Dispatcher.CurrentDispatcher != _uiDispatcher)
        {
            _uiDispatcher.Invoke(delegate()
            {
                // UI thread code
            });
        }
        else
        {
             // UI thread code
        }
    }
    

    以下是itwolson的想法,它是一种扩展方法,对我来说非常有用:

    /// <summary>Extension methods for EventHandler-type delegates.</summary>
    public static class EventExtensions
    {
        /// <summary>Raises the event (on the UI thread if available).</summary>
        /// <param name="multicastDelegate">The event to raise.</param>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">An EventArgs that contains the event data.</param>
        /// <returns>The return value of the event invocation or null if none.</returns>
        public static object Raise(this MulticastDelegate multicastDelegate, object sender, EventArgs e)
        {
            object retVal = null;
    
            MulticastDelegate threadSafeMulticastDelegate = multicastDelegate;
            if (threadSafeMulticastDelegate != null)
            {
                foreach (Delegate d in threadSafeMulticastDelegate.GetInvocationList())
                {
                    var synchronizeInvoke = d.Target as ISynchronizeInvoke;
                    if ((synchronizeInvoke != null) && synchronizeInvoke.InvokeRequired)
                    {
                        retVal = synchronizeInvoke.EndInvoke(synchronizeInvoke.BeginInvoke(d, new[] { sender, e }));
                    }
                    else
                    {
                        retVal = d.DynamicInvoke(new[] { sender, e });
                    }
                }
            }
    
            return retVal;
        }
    }
    

    我非常喜欢Mike Bouk的答案(+1),我把它合并到了我的代码库中。我担心,由于参数不匹配,如果调用的委托不是EventHandler委托,则他的DynamicInvoke调用将引发运行时异常。由于您处于后台线程中,我假设您可能希望异步调用UI方法,并且您不关心它是否完成

    下面的我的版本只能与EventHandler委托一起使用,并且将忽略其调用列表中的其他委托。由于EventHandler委托不返回任何内容,因此我们不需要结果。这允许我在异步进程完成后通过在BeginInvoke调用中传递EventHandler来调用EndInvoke。调用将通过AsynchronousCallback返回IAsyncResult.AsynchState中的此EventHandler,此时调用EventHandler.EndInvoke

    /// <summary>
    /// Safely raises any EventHandler event asynchronously.
    /// </summary>
    /// <param name="sender">The object raising the event (usually this).</param>
    /// <param name="e">The EventArgs for this event.</param>
    public static void Raise(this MulticastDelegate thisEvent, object sender, 
        EventArgs e)
    {
      EventHandler uiMethod; 
      ISynchronizeInvoke target; 
      AsyncCallback callback = new AsyncCallback(EndAsynchronousEvent);
    
      foreach (Delegate d in thisEvent.GetInvocationList())
      {
        uiMethod = d as EventHandler;
        if (uiMethod != null)
        {
          target = d.Target as ISynchronizeInvoke; 
          if (target != null) target.BeginInvoke(uiMethod, new[] { sender, e }); 
          else uiMethod.BeginInvoke(sender, e, callback, uiMethod);
        }
      }
    }
    
    private static void EndAsynchronousEvent(IAsyncResult result) 
    { 
      ((EventHandler)result.AsyncState).EndInvoke(result); 
    }
    

    我发现依赖作为EventHandler的方法并不总是有效,而且ISynchronizeInvoke也不适用于WPF。因此,我的尝试是这样的,它可能会帮助某些人:

    public static class Extensions
    {
        // Extension method which marshals events back onto the main thread
        public static void Raise(this MulticastDelegate multicast, object sender, EventArgs args)
        {
            foreach (Delegate del in multicast.GetInvocationList())
            {
                // Try for WPF first
                DispatcherObject dispatcherTarget = del.Target as DispatcherObject;
                if (dispatcherTarget != null && !dispatcherTarget.Dispatcher.CheckAccess())
                {
                    // WPF target which requires marshaling
                    dispatcherTarget.Dispatcher.BeginInvoke(del, sender, args);
                }
                else
                {
                    // Maybe its WinForms?
                    ISynchronizeInvoke syncTarget = del.Target as ISynchronizeInvoke;
                    if (syncTarget != null && syncTarget.InvokeRequired)
                    {
                        // WinForms target which requires marshaling
                        syncTarget.BeginInvoke(del, new object[] { sender, args });
                    }
                    else
                    {
                        // Just do it.
                        del.DynamicInvoke(sender, args);
                    }
                }
            }
        }
        // Extension method which marshals actions back onto the main thread
        public static void Raise<T>(this Action<T> action, T args)
        {
            // Try for WPF first
            DispatcherObject dispatcherTarget = action.Target as DispatcherObject;
            if (dispatcherTarget != null && !dispatcherTarget.Dispatcher.CheckAccess())
            {
                // WPF target which requires marshaling
                dispatcherTarget.Dispatcher.BeginInvoke(action, args);
            }
            else
            {
                // Maybe its WinForms?
                ISynchronizeInvoke syncTarget = action.Target as ISynchronizeInvoke;
                if (syncTarget != null && syncTarget.InvokeRequired)
                {
                    // WinForms target which requires marshaling
                    syncTarget.BeginInvoke(action, new object[] { args });
                }
                else
                {
                    // Just do it.
                    action.DynamicInvoke(args);
                }
            }
        }
    }
    
    公共静态类扩展
    {
    //将事件封送回主线程的扩展方法
    public static void Raise(此多播代理多播、对象发送方、事件args args)
    {
    foreach(多播中的委托del.GetInvocationList())
    {
    //先试试WPF
    DispatcherObject dispatcherTarget=del.Target作为DispatcherObject;
    if(dispatcherTarget!=null&&!dispatcherTarget.Dispatcher.CheckAccess())
    {
    //需要封送处理的WPF目标
    dispatcherTarget.Dispatcher.BeginInvoke(del、sender、args);
    }
    其他的
    {
    //也许是WinForms?
    ISynchronizeInvoke syncTarget=del.Target作为ISynchronizeInvoke;
    if(syncTarget!=null&&syncTarget.invokererequired)
    {
    //需要封送处理的WinForms目标
    syncTarget.BeginInvoke(del,新对象[]{sender,args});
    }
    其他的
    {
    //就这么做吧。
    del.DynamicInvoke(发送方,args);
    }
    }
    }
    }
    //将操作封送回主线程的扩展方法
    公共静态无效提升(此操作,T参数)
    {
    //先试试WPF
    DispatcherObject dispatcherTarget=作为DispatcherObject的action.Target;
    if(dispatcherTarget!=null&&!dispatcherTarget.Dispatcher.CheckAccess())
    {
    //需要封送处理的WPF目标
    dispatcherTarget.Dispatcher.BeginInvoke(操作,参数);
    }
    其他的
    {
    //也许是WinForms?
    ISynchronizeInvoke syncTarget=操作。目标为ISynchronizeInvoke;
    if(syncTarget!=null&&syncTarget.invokererequired)
    {
    //需要封送处理的WinForms目标
    syncTarget.BeginInvoke(操作,新对象[]{args});
    }
    其他的
    {
    //就这么做吧。
    动作.动态Voke(args);
    }
    }
    }
    }
    
    我喜欢这些答案和示例,但按照标准,您编写的库都是错的。不要为了其他线程而将事件封送到其他线程,这一点很重要。让你的事件在哪里触发,在哪里处理。当该事件需要更改线程时,让最终开发人员在该时间点进行更改是很重要的。

    我知道这是一个旧线程,但鉴于它确实帮助我开始构建类似的线程,因此我想分享我的代码。乌辛
    MyEventHandlerEvent.Raise(this, MyEventArgs);
    
    public static class Extensions
    {
        // Extension method which marshals events back onto the main thread
        public static void Raise(this MulticastDelegate multicast, object sender, EventArgs args)
        {
            foreach (Delegate del in multicast.GetInvocationList())
            {
                // Try for WPF first
                DispatcherObject dispatcherTarget = del.Target as DispatcherObject;
                if (dispatcherTarget != null && !dispatcherTarget.Dispatcher.CheckAccess())
                {
                    // WPF target which requires marshaling
                    dispatcherTarget.Dispatcher.BeginInvoke(del, sender, args);
                }
                else
                {
                    // Maybe its WinForms?
                    ISynchronizeInvoke syncTarget = del.Target as ISynchronizeInvoke;
                    if (syncTarget != null && syncTarget.InvokeRequired)
                    {
                        // WinForms target which requires marshaling
                        syncTarget.BeginInvoke(del, new object[] { sender, args });
                    }
                    else
                    {
                        // Just do it.
                        del.DynamicInvoke(sender, args);
                    }
                }
            }
        }
        // Extension method which marshals actions back onto the main thread
        public static void Raise<T>(this Action<T> action, T args)
        {
            // Try for WPF first
            DispatcherObject dispatcherTarget = action.Target as DispatcherObject;
            if (dispatcherTarget != null && !dispatcherTarget.Dispatcher.CheckAccess())
            {
                // WPF target which requires marshaling
                dispatcherTarget.Dispatcher.BeginInvoke(action, args);
            }
            else
            {
                // Maybe its WinForms?
                ISynchronizeInvoke syncTarget = action.Target as ISynchronizeInvoke;
                if (syncTarget != null && syncTarget.InvokeRequired)
                {
                    // WinForms target which requires marshaling
                    syncTarget.BeginInvoke(action, new object[] { args });
                }
                else
                {
                    // Just do it.
                    action.DynamicInvoke(args);
                }
            }
        }
    }
    
    public static void ThreadAwareRaise<TEventArgs>(this EventHandler<TEventArgs> customEvent,
        object sender, TEventArgs e) where TEventArgs : EventArgs
    {
        foreach (var d in customEvent.GetInvocationList().OfType<EventHandler<TEventArgs>>())
            switch (d.Target)
            {
                case DispatcherObject dispatchTartget:
                    dispatchTartget.Dispatcher.BeginInvoke(d, sender, e);
                    break;
                case ISynchronizeInvoke syncTarget when syncTarget.InvokeRequired:
                    syncTarget.BeginInvoke(d, new[] {sender, e});
                    break;
                default:
                    d.Invoke(sender, e);
                    break;
            }
    }