如何在正在运行的线程上调用方法?

时间:2015-02-27 19:32:26

标签: c# multithreading

在控制台应用程序上,我正在启动一个线程数组。线程传递一个对象并在其中运行一个方法。我想知道如何在单个运行线程内的对象上调用方法。

调度员不起作用。 SynchronizationContext"发送"在调用线程上运行" Post"使用新线程。我希望能够在运行的目标线程上调用该方法并在正在运行的线程上传递参数,而不是调用线程。

更新2:示例代码

using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace CallingFromAnotherThread
{
    class Program
    {
        static void Main(string[] args)
        {
            var threadCount = 10;
            var threads = new Thread[threadCount];
            Console.WriteLine("Main on Thread " + Thread.CurrentThread.ManagedThreadId);
            for (int i = 0; i < threadCount; i++)
            {
                Dog d = new Dog();
                threads[i] = new Thread(d.Run);
                threads[i].Start();
            }
            Thread.Sleep(5000);

            //how can i call dog.Bark("woof");
            //on the individual dogs and make sure they run on the thread they were created on.
            //not on the calling thread and not on a new thread.
        }

    }

    class Dog
    {
        public void Run()
        {
            Console.WriteLine("Running on Thread " + Thread.CurrentThread.ManagedThreadId);
        }

        public void Bark(string text)
        {
            Console.WriteLine(text);
            Console.WriteLine("Barking on Thread " + Thread.CurrentThread.ManagedThreadId);
        }
    }
}

更新1: 使用synchronizationContext.Send结果使用调用线程

Channel created
Main thread  10
SyncData Added for thread 11
Consuming channel ran on thread 11   
Calling AddConsumer on thread 10
Consumer added consumercb78b. Executed on thread 10
Calling AddConsumer on thread 10
Consumer added consumer783c4. Executed on thread 10

使用synchronizationContext.Post结果使用不同的线程

Channel created
Main thread  10
SyncData Added for thread 11
Consuming channel ran on thread 11   
Calling AddConsumer on thread 12
Consumer added consumercb78b. Executed on thread 6
Calling AddConsumer on thread 10
Consumer added consumer783c4. Executed on thread 7

3 个答案:

答案 0 :(得分:4)

目标线程必须运行代码&#34;自身&#34; - 或者它只是跨线程访问对象。这是通过目标线程本身的某种形式的事件调度循环来完成的。

如果基础提供程序支持可以 支持此 例如,在WinForms或WPF(它们自己使用&#34;窗口消息泵&#34;)中使用Post将&#34;在UI线程上运行&#34;。

基本上,所有这些结构都遵循模式的一些变化:

// On "target thread"
while (running) {
   var action = getNextDelegateFromQueue();
   action();
}

// On other thread
postDelegateToQueue(actionToDoOnTargetThread);

手动创建原语队列系统非常简单 - 只需确保使用正确的同步保护。 (虽然我确信有一些整洁的问题&#34;库存在那里;包括将所有内容包装到SynchronizationContext中。)


这是手动队列的原语版本。请注意,可能 1 竞争条件..但是,FWIW:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace DogPark
{
    internal class DogPark
    {

        private readonly string _parkName;
        private readonly Thread _thread;
        private readonly ConcurrentQueue<Action> _actions = new ConcurrentQueue<Action>();
        private volatile bool _isOpen;

        public DogPark(string parkName)
        {
            _parkName = parkName;
            _isOpen = true;
            _thread = new Thread(OpenPark);
            _thread.Name = parkName;
            _thread.Start();
        }

        // Runs in "target" thread
        private void OpenPark(object obj)
        {
            while (true)
            {
                Action action;
                if (_actions.TryDequeue(out action))
                {
                    Program.WriteLine("Something is happening at {0}!", _parkName);
                    try
                    {
                        action();
                    }
                    catch (Exception ex)
                    {
                        Program.WriteLine("Bad dog did {0}!", ex.Message);
                    }
                }
                else
                {
                    // Nothing left!
                    if (!_isOpen && _actions.IsEmpty)
                    {
                        return;
                    }
                }

                Thread.Sleep(0); // Don't toaster CPU
            }
        }

        // Called from external thread
        public void DoItInThePark(Action action)
        {
            if (_isOpen)
            {
                _actions.Enqueue(action);
            }
        }

        // Called from external thread
        public void ClosePark()
        {
            _isOpen = false;
            Program.WriteLine("{0} is closing for the day!", _parkName);
            // Block until queue empty.
            while (!_actions.IsEmpty)
            {
                Program.WriteLine("Waiting for the dogs to finish at {0}, {1} actions left!", _parkName, _actions.Count);

                Thread.Sleep(0); // Don't toaster CPU
            }
            Program.WriteLine("{0} is closed!", _parkName);
        }

    }

    internal class Dog
    {

        private readonly string _name;

        public Dog(string name)
        {
            _name = name;
        }

        public void Run()
        {
            Program.WriteLine("{0} is running at {1}!", _name, Thread.CurrentThread.Name);
        }

        public void Bark()
        {
            Program.WriteLine("{0} is barking at {1}!", _name, Thread.CurrentThread.Name);
        }

    }

    internal class Program
    {
        // "Thread Safe WriteLine"
        public static void WriteLine(params string[] arguments)
        {
            lock (Console.Out)
            {
                Console.Out.WriteLine(arguments);
            }
        }

        private static void Main(string[] args)
        {
            Thread.CurrentThread.Name = "Home";

            var yorkshire = new DogPark("Yorkshire");
            var thunderpass = new DogPark("Thunder Pass");

            var bill = new Dog("Bill the Terrier");
            var rosemary = new Dog("Rosie");

            bill.Run();

            yorkshire.DoItInThePark(rosemary.Run);
            yorkshire.DoItInThePark(rosemary.Bark);

            thunderpass.DoItInThePark(bill.Bark);

            yorkshire.DoItInThePark(rosemary.Run);

            thunderpass.ClosePark();
            yorkshire.ClosePark();
        }

    }
}

输出应该如下所示 - 请记住,由于非同步线程的固有特性,当运行多次时,这将更改

Bill the Terrier is running at Home!
Something is happening at Thunder Pass!
Something is happening at Yorkshire!
Rosie is running at Yorkshire!
Bill the Terrier is barking at Thunder Pass!
Something is happening at Yorkshire!
Rosie is barking at Yorkshire!
Something is happening at Yorkshire!
Rosie is running at Yorkshire!
Thunder Pass is closing for the day!
Thunder Pass is closed!
Yorkshire is closing for the day!
Yorkshire is closed!

没有什么能阻止狗同时在多个狗公园演出。


1 存在竞争条件,就是这样:公园可能会在最后一次狗行动开始前关闭。

这是因为狗停放线程在动作运行之前使行动出列 - 并且关闭狗场的方法仅等待所有动作都出列。

有多种方法可以解决这个问题,例如:

  • 并发队列首先可以先查看 - 使用 - 然后 - 行动后出列,或
  • 可以使用单独的volatile isClosed-for-real标志(从狗公园线程设置),或者..

我已经将 bug 留下来作为线程危险的提醒..

答案 1 :(得分:1)

正在运行的线程已在执行某个方法。您不能直接强制该线程离开该方法并输入一个新方法。但是,您可以向该线程发送信息以保留当前方法并执行其他操作。但这只有在执行的方法可以对传递的信息作出反应时才有效 通常,您可以使用线程来调用/执行方法,但不能在正在运行的线程上调用方法。

根据您的更新进行修改:
如果你想使用相同的线程来执行dog.run和dog.bark,并在相同的对象中执行,你需要修改你的代码:

static void Main(string[] args)
{
  var threadCount = 10;
  var threads = new Thread[threadCount];
  Console.WriteLine("Main on Thread " + Thread.CurrentThread.ManagedThreadId);

  // keep the dog objects outside the creation block in order to access them later again. Always useful.
  Dog[] dogs = New Dog[threadCount];

  for (int i = 0; i < threadCount; i++)
  {
    dogs[i] = new Dog();
    threads[i] = new Thread(d.Run);
    threads[i].Start();
  }
  Thread.Sleep(5000);

  //how can i call dog.Bark("woof") --> here you go:
  for (int i = 0; i < threadCount; i++)
  {
    threads[i] = new Thread(d.Bark);
    threads[i].Start();
  }
  // but this will create NEW threads because the others habe exited after finishing d.run, and habe been deleted. Is this a problem for you?
  // maybe the other threads are still running, causing a parallel execution of d.run and d.bark.

  //on the individual dogs and make sure they run on the thread they were created on.
  //not on the calling thread and not on a new thread. -->

  // instead of d.run, call d.doActions and loop inside that function, check for commands from external sources:
  for (int i = 0; i < threadCount; i++)
  {
    threads[i] = new Thread(d.doActions);
    threads[i].Start();
  }
  // but in this case there will be sequential execution of actions. No parallel run and bark.
}

在你的狗课内:

Enum class EnumAction
{
  Nothing,
  Run,
  bark,
  exit,
};

EnumAction m_enAction;
Object m_oLockAction;

void SetAction (EnumAction i_enAction)
{
  Monitor.Enter (m_oLockAction);
  m_enAction = i_enAction;
  Monitor.Exit (m_oLockAction);
}
void SetAction (EnumAction i_enAction)
{
  Monitor.Enter (m_oLockAction);
  m_enAction = i_enAction;
  Monitor.Exit (m_oLockAction);
}

Void doActions()
{
  EnumAction enAction;
  Do
  {
    Thread.sleep(20);
    enAction = GetAction();
    Switch(enAction)
    {
    Case EnumAction.run:
      Run(); break;
    Case ...
    }
  } while (enAction != EnumAction.exit);
}

知道了吗? ; - )

对不起任何打字错误,我在手机上打字,我通常使用C ++ CLI。

另一个建议:正如您将读取线程内的变量m_enAction并从外部编写它,您需要确保它由于来自不同线程的访问而得到正确更新。线程绝不能将变量缓存在CPU中,否则它们看不到变化。使用锁(例如Monitor)来实现这一目标。 (但是不要在m_enAction上使用监视器,因为你只能在对象上使用监视器。为此目的创建一个虚拟对象。)
我添加了必要的代码。查看编辑之间的差异以查看更改。

答案 2 :(得分:0)

第一种方法运行时,无法运行第二种方法。如果您希望它们并行运行,则需要另一个线程。但是,您的对象需要是线程安全的。 线程的执行仅仅意味着执行指令序列。 Dispatcher只不过是一个接一个地执行排队方法的无限循环。

我建议您使用任务而不是线程。使用Parallel.ForEach在每个dog对象实例上运行Dog.Run方法。要运行Bark方法,请使用Task.Run(dog.Bark)。

由于您使用跑步和吠叫狗作为示例,您可以编写自己的“调度员”。这意味着无限循环将执行所有排队的工作。在这种情况下,你可以让所有的狗都在单线程中。听起来很奇怪,但你可以拥有无​​限量的狗。最后,只有许多线程可以在许多CPU核心可用的同时执行