C#非UI跨线程调用

时间:2010-11-17 21:29:30

标签: c# multithreading invoke

我正在尝试用C#进行跨线程调用。

每当我从一个从线程B调用的静态方法调用在线程A的上下文中创建的对象的方法时,该方法总是在线程B中运行。我不希望这样,我希望它在同一个线程上运行作为线程我正在调用其方法的对象。

Invoke适用于UI调用,我读过很多文章和SO答案,涉及制作跨线程Forms / WPF调用的不同方法。无论我尝试什么(事件处理,委托等)线程A的对象的方法将始终在线程B中运行,如果它由线程B调用。

我应该寻找图书馆的哪一部分来解决这个问题?如果相关,则线程B当前“旋转”,从网络端口读取并偶尔通过在线程A中创建并使用ParameterizedThreadStart传入的委托来调用线程A的对象方法。

我不打算改变范例,只是从一个线程(线程B)向另一个线程(线程A)发送消息(调用方法的请求)。

编辑:

我的问题是“我应该在图书馆的哪个部分来解决这个问题?”答案似乎是没有。如果我想清楚地描述消费和民意调查,我将不得不编写自己的代码来处理这个问题。

4 个答案:

答案 0 :(得分:6)

  

每当我调用在线程A上运行的对象的方法时

对象不在线程上运行。

为了使它能够工作,你必须创建某种类型的队列,你可以将一个委托推送到那个常规检查线程A的主循环中。这样的事情,假设Something.MainThreadLoop是线程A的入口点:

public class Something
{
    private Queue<Action> actionQueue = new Queue<Action>();

    private volatile bool threadRunning = true;

    public void RunOnThread(Action action)
    {
        if (action == null)
            throw new ArgumentNullException("action");

        lock (actionQueue)
            actionQueue.Enqueue(action);
    }

    public void Stop()
    {
        threadRunning = false;
    }

    private void RunPendingActions()
    {
        while (actionQueue.Count > 0) {
            Action action;

            lock (actionQueue)
                action = actionQueue.Dequeue();

            action();
        }
    }

    public void MainThreadLoop()
    {
        while (threadRunning) {
            // Do the stuff you were already doing on this thread.

            // Then, periodically...
            RunPendingActions();
        }
    }
}

然后,给定对Something对象的引用,你可以这样做:

something.RunOnThread(() => Console.WriteLine("I was printed from thread A!"));

答案 1 :(得分:3)

代码在线程上运行。对象不是(通常 - 参见线程本地)绑定到特定线程。通过执行WinFormControl.Invoke或WPFControl.Invoke,您将分别向Message Pump或Dispatcher发布消息,以便在以后运行某些代码。

消息泵是这样的:

Message message;
while(GetMessage(&message))
{
    ProcessMessage(message);
}

Microsoft专门构建了他们的UI控件和项目,以允许跨线程发布消息。从线程A调用方法将始终在线程A上执行该方法,即使它最终执行某种异步工作并提前返回。

编辑:

我认为你需要的是生产者消费者模式。

http://msdn.microsoft.com/en-us/library/yy12yx1f(VS.80).aspx

忘记使用主线程中的消息,这听起来像你想要做的。从线程C中消费。

线程A正在做'更重要的事情'。线程B正在旋转,正在侦听消息。线程C正在使用这些消息。

无需跨线程编组。

答案 2 :(得分:1)

你需要做的就是推出一种Queue并让Thread A watch排队等候工作。当线程A看到新工作进入队列时,它可以将它出列并完成工作,然后返回等待更多。

这是一些伪代码:

    public class ThreadAQueue
    {
        private Queue<delegate> _queue;
        private bool _quitWorking;

        public void EnqueueSomeWork(delegate work)
        {
            lock(_queue) 
            {
                _queue.Enqueue(work);
            }
        }

        private void DoTheWork()
        {
            while(!quitWorking) 
            {
                delegate myWork;

                lock(_queue)
                {
                    if(_queue.Count > 1)
                        myWork = _queue.Dequeue();
                }

                myWork();
            }
        }
    }

答案 3 :(得分:1)

编辑:我想您可能想要使用System.Threading.AutoResetEvent类。 MSDN文档有一个很好的例子,一个线程在另一个线程上等待我认为类似于你想要做的事情:http://msdn.microsoft.com/en-us/library/system.threading.autoresetevent.aspx 特别要注意对trigger.WaitOne()和trigger.Set()

的调用

EDIT2:在阅读OP的新评论后添加了选项#3。

“每当我调用在线程A上运行的对象的方法时......” - 一个对象不会在一个线程上“运行”,并且不管任何线程创建该对象,它都不是任何线程所拥有的。

鉴于您的问题是关于“非UI跨线程调用”,我假设您已经熟悉“UI跨线程调用”。我可以看到WinForms如何给你一个线程拥有一个对象的印象,你需要“向一个线程发送一条消息”以使它做一些事情。

WinForm控件对象是一种特殊情况,因为如果你用一个不是创建它们的线程与它们交互它们就不能正常工作,但这不是由线程和对象的方式引起的交互。

无论如何,要解决你的问题。

首先,一个澄清问题的问题:你已经提到了线程B正在做什么,但线程A在被线程B“调用”之前做了什么?

我认为以下是您想要做的几个想法:

  1. 在您需要之前不要创建线程A.而不是让线程B“向线程A发送消息”,而是让线程B创建线程A(或者如果你愿意的话,将其称为线程C)并使其在那时开始执行。

  2. 如果您需要线程A已经存在并且您只希望线程A一次处理一个线程B的事件,您可以让线程A等待,直到它收到来自线程B的通知。看看{ {3}}(感兴趣的派生类是ManualResetEvent和AutoResetEvent)。

    线程A会在某个时刻调用WaitHandle.WaitOne(),这会导致它暂停并等待,直到线程B在同一个WaitHandle对象上调用WaitHandle.Set()。

  3. 如果线程A忙于做其他事情,那么您可能想要设置某种标志变量。与#2中的WaitHandle概念类似,但不是让线程A暂停,而只是希望线程B设置一个标志(可能只是一个布尔变量),它将向线程A发出需要做某事的信号。当线程A忙于做其他事情时,它可以定期检查该标志,以决定是否有工作需要完成。

  4. 线程A在对象上执行的方法是否需要来自线程B的任何输入?然后在线程B调用WaitHandle.Set()之前,让它将一些数据粘贴到队列或其他东西中。然后,当线程A被“激活”时,它可以从队列中检索该数据并继续使用该数据执行对象的方法。使用锁机制(即C#lock语句)来同步对队列的访问。