【问题标题】:Thread switching线程切换
【发布时间】:2011-07-12 19:03:55
【问题描述】:

如何在调用异步函数的同一线程中执行回调方法。 调用者线程可能不是 UI 线程...但 UI 不应该挂起..

感谢和问候, 饭菜

【问题讨论】:

    标签: multithreading c#-2.0


    【解决方案1】:

    没有什么灵丹妙药可以让一个线程在另一个线程上启动委托的执行。目标线程必须专门构造以允许这样做。在 UI 线程的情况下,有一个消息泵来分派和处理消息。此消息泵可用于通过ISynchronizeInvoke 接口执行封送操作。

    ISynchronizeInvoke target = someForm; // where someForm is a Form or Control
    target.Invoke(
      (Action)(() =>
      {
        MessageBox.Show("I am on the target thread");
      }), null);
    

    在您的情况下,调用异步函数的线程必须具有某种内置的生产者-消费者机制,以便在工作线程指示它执行此操作后,在该线程上异步执行回调。不幸的是,这不是一个容易解决的问题。

    这是创建一个可以接受要执行的委托的线程的一种方法。

    public class SynchronizeInvokeThread : ISynchronizeInvoke
    {
        private Thread m_Thread;
        private BlockingCollection<WorkItem> m_Collection = new BlockingCollection<WorkItem>();
    
        public SynchronizeInvokeThread()
        {
            m_Thread = new Thread(
                () =>
                {
                    SynchronizationContext.SetSynchronizationContext(new MySynchronizationContext(this));
                    while (true)
                    {
                        WorkItem wi = m_Collection.Take();
                        wi.Complete(wi.Method.DynamicInvoke(wi.Args));
                    }
                });
            m_Thread.Start();
        }
    
        public IAsyncResult BeginInvoke(Delegate method, object[] args)
        {
            var wi = new WorkItem(method, args);
            m_Collection.Add(wi);
            return wi;
        }
    
        public object EndInvoke(IAsyncResult result)
        {
            var wi = (WorkItem)result;
            wi.AsyncWaitHandle.WaitOne();
            return wi.Result;
        }
    
        public object Invoke(Delegate method, object[] args)
        {
            var wi = new WorkItem(method, args);
            m_Collection.Add(wi);
            wi.AsyncWaitHandle.WaitOne();
            return wi.Result;
        }
    
        public bool InvokeRequired
        {
            get { return Thread.CurrentThread != m_Thread; }
        }
    
        private class MySynchronizationContext : SynchronizationContext
        {
            private ISynchronizeInvoke m_SynchronizingObject;
    
            public MySynchronizationContext(ISynchronizeInvoke synchronizingObject)
            {
                m_SynchronizingObject = synchronizingObject;
            }
    
            public override void Post(SendOrPostCallback d, object state)
            {
                m_SynchronizingObject.BeginInvoke(d, new object[] { state });
            }
    
            public override void Send(SendOrPostCallback d, object state)
            {
                m_SynchronizingObject.Invoke(d, new object[] { state });
            }
        }
    
        private class WorkItem : IAsyncResult
        {
            private Delegate m_Method;
            private object[] m_Args;
            private object m_Result = null;
            private ManualResetEvent m_Signal = new ManualResetEvent(false);
    
            public WorkItem(Delegate method, object[] args)
            {
                m_Method = method;
                m_Args = args;
            }
    
            public void Complete(object result)
            {
                m_Result = result;
                m_Signal.Set();
            }
    
            public object Result
            {
                get { return m_Result; }
            }
    
            public Delegate Method
            {
                get { return m_Method; }
            }
    
            public object[] Args
            {
                get { return m_Args; }
            }
    
            public object AsyncState
            {
                get { return null; }
            }
    
            public WaitHandle AsyncWaitHandle
            {
                get { return m_Signal; }
            }
    
            public bool CompletedSynchronously
            {
                get { return false; }
            }
    
            public bool IsCompleted
            {
                get { return m_Signal.WaitOne(0); }
            }
        }
    }
    

    可以这样使用。

    ISynchronizeInvoke target = new SynchronizeInvokeThread(); 
    target.Invoke(
      (Action)(() =>
      {
        Console.WriteLine("I am on the target thread");
        SynchronizationContext.Current.Post(
          (state) =>
          {
            Console.WriteLine("I even have a synchronization context!");
          }, null);
      }), null);
    

    更新:

    根据下面的评论 BlockingCollection 仅在 .NET 4.0 中可用或作为 Reactive Extensions 下载的一部分。如果这个数据结构对你不可用,那么这个已经很困难的代码就变得更加困难了。

    【讨论】:

    • @Brain Gideon:使用CustomThread 不会调用目标线程中的代码。这是因为您没有使用SynchronizationContext... 来证明这一点。在 Windows 窗体上运行测试并将Console.WriteLine 替换为textBox1.Text = "some data";,您将遇到跨线程异常。另外请注意,您使用的是ConcurrentCollection,其中问题标签为C# 3.0
    • 目标线程是CustomThread,它确实将委托编组到那个线程。我的观点之一是要演示让它与非 UI 线程一起工作涉及多少,因为 OP 确实声明非 UI 线程可能是目标。发生异常是因为您尝试从托管线程以外的线程访问Control,但这仅证明目标线程是非 UI 线程。如果您希望目标线程成为 UI 线程,您显然会改用 Control.Invoke。不过 C# 3.0 的公平点:)
    • @Brain:不,即使在控制台主控制台的控制台应用程序测试中也不起作用:Console.WriteLine("Thread Id: \"{0}\"", Thread.CurrentThread.ManagedThreadId); CustomThread thread = new CustomThread(); thread.Invoke( (Action)(() =&gt; { Console.WriteLine("Thread Id: \"{0}\"", Thread.CurrentThread.ManagedThreadId); }), null); 输出将类似于:Thread Id: 9 Thread Id: 10
    • @Jalal:确实有效。我在发布之前对其进行了测试。你观察到的正是应该发生的。您能够从线程 9(主)向线程 10(目标)注入方法调用。这清楚地证明我们已经创建了一个非 UI 目标线程,它可以像普通 UI 线程一样使用 ISynchronizeInvoke 接受委托注入。
    • @Jalal:我认为SynchronizationContext 可能有些混淆。并非所有线程都有一个。事实上,大多数都没有。您必须实际继承SynchronizationContext,然后将其附加到目标线程,然后才能从SynchronizationContext.Current 中提取它。我更新了答案以提供目标线程上的上下文,这很容易,因为我已经准备好让目标线程接受委托注入的管道。
    【解决方案2】:

    使用 BackgroundWorker。回调将在所属线程上。

    如果异步操作需要在回调之后继续运行,您可以使用 WPF System.Windows.Application.Current.Dispatcher.Invoke/BeginInvoke 进行多个回调...或者如果是 WinForms,您可以使用窗体或控件实例本身并调用 Invoke/BeginInvoke。

    【讨论】:

    • 如果是控制台应用程序,则在不同线程中调用 BackgroundWorker 的 Completed 事件。在其中我们调用 RunAsync 函数..
    【解决方案3】:

    就像提到的Brian Gideon 一样,您应该使用ISynchronizeInvoke“System.ComponentModel.ISynchronizeInvoke”。在要在另一个线程上编组其线程执行的类上实现它。这里的例子Mediaclass“我实现的一些类正在与Com对象交互,所以它应该在主线程中执行它的方法”;由于它使用System.Threading.SynchronizationContext.Current 的类实现,因此您可以在 WindowsForms 中使用它,但不能在控制台应用程序中使用它,因为 System.Threading.SynchronizationContext.Current 为空。

    当您想将此类的执行编组到创建它的线程中时,只需调用其Invoke 方法即可。

    public abstract class Media : ISynchronizeInvoke
    {
            //....
    
            private readonly System.Threading.SynchronizationContext _currentContext = System.Threading.SynchronizationContext.Current;
    
            private readonly System.Threading.Thread _mainThread = System.Threading.Thread.CurrentThread;
    
            private readonly object _invokeLocker = new object();
            //....
    
    
            #region ISynchronizeInvoke Members
    
            public bool InvokeRequired
            {
                get
                {
                    return System.Threading.Thread.CurrentThread.ManagedThreadId != this._mainThread.ManagedThreadId;
                }
            }
    
            /// <summary>
            /// This method is not supported!
            /// </summary>
            /// <param name="method"></param>
            /// <param name="args"></param>
            /// <returns></returns>
            [Obsolete("This method is not supported!", true)]
            public IAsyncResult BeginInvoke(Delegate method, object[] args)
            {
                throw new NotSupportedException("The method or operation is not implemented.");
            }
    
            /// <summary>
            /// This method is not supported!
            /// </summary>
            /// <param name="method"></param>
            /// <param name="args"></param>
            /// <returns></returns>
            [Obsolete("This method is not supported!", true)]
            public object EndInvoke(IAsyncResult result)
            {
                throw new NotSupportedException("The method or operation is not implemented.");
            }
    
            public object Invoke(Delegate method, object[] args)
            {
                if (method == null)
                {
                    throw new ArgumentNullException("method");
                }
    
                lock (_invokeLocker)
                {
                    object objectToGet = null;
    
                    SendOrPostCallback invoker = new SendOrPostCallback(
                    delegate(object data)
                    {
                        objectToGet = method.DynamicInvoke(args);
                    });
    
                    _currentContext.Send(new SendOrPostCallback(invoker), method.Target);
    
                    return objectToGet;
                }
            }
    
            public object Invoke(Delegate method)
            {
                return Invoke(method, null);
            }
    
            #endregion//ISynchronizeInvoke Members
    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-07-19
      • 2013-10-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-23
      相关资源
      最近更新 更多