Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to call a method on a running thread?

On a console application, i am currently starting an array of threads. The thread is passed an object and running a method in it. I would like to know how to call a method on the object inside the individual running threads.

Dispatcher doesn't work. SynchronizationContext "Send" runs on the calling thread and "Post" uses a new thread. I would like to be able to call the method and pass parameters on a running thread on the target thread it's running on and not the calling thread.

Update 2: Sample code

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);
        }
    }
}

Update 1: Using synchronizationContext.Send results to using the calling thread

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

Using synchronizationContext.Post results to using a different thread

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
like image 931
bwisitero Avatar asked Jan 09 '23 19:01

bwisitero


2 Answers

The target thread must run the code "on itself" - or it is just accessing the object across threads. This is done with some form of event dispatch loop on the target thread itself.

The SynchronizationContext abstraction can and does support this if the underlying provider supports it. For example in either WinForms or WPF (which themselves use the "window message pump") using Post will "run on the UI thread".

Basically, all such constructs follow some variation of the pattern:

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

// On other thread
postDelegateToQueue(actionToDoOnTargetThread);

It is fairly simple to create a primitive queue system manually - just make sure to use the correct synchronization guards. (Although I am sure there are tidy "solved problem" libraries out there; including wrapping everything up into a SynchronizationContext.)


Here is a primitive version of the manual queue. Note that there may be is1 a race condition.. but, 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();
        }

    }
}

The output should look about like the following - keep in mind that this will change when run multiples times due to the inherent nature of non-synchronized threads.

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!

There is nothing preventing a dog from performing at multiple dog parks simultaneously.


1 There is a race condition present and it is this: a park may close before the last dog action runs.

This is because the dog park thread dequeues the action before the action is run - and the method to close the dog park only waits until all the actions are dequeued.

There are multiple ways to address it, for instance:

  • The concurrent queue could first peek-use-then-dequeue-after-the-action, or
  • A separate volatile isClosed-for-real flag (set from the dog park thread) could be used, or ..

I've left the bug in as a reminder of the perils of threading..

like image 92
user2864740 Avatar answered Jan 15 '23 04:01

user2864740


A running thread is already executing a method. You cannot directly force that thread to leave the method and enter a new one. However, you could send information to that thread to leave the current method and do something else. But this only works if the executed method can react on that passed information.
In general, you can use threads to call/execute methods, but you cannot call a method ON a running thread.

Edit, based on your updates:
If you want to use the same threads to execute dog.run and dog.bark, and do it in the same objects, the you need to modify your code:

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.
}

Inside your dog class:

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);
}

Got it? ;-)

Sorry for any typos, I was typing on my mobile phone, and I usually use C++CLI.

Another advice: as you would read the variable m_enAction inside the thread and write it from outside, you need to ensure that it gets updated properly due to the access from different threads. The threads MUST NOT cache the variable in the CPU, otherwise they don't see it changing. Use locks (e.g. Monitor) to achieve that. (But do not use a Monitor on m_enAction, because you can use Monitors only on objects. Create a dummy object for this purpose.)
I have added the necessary code. Check out the differences between the edits to see the changes.

like image 43
Tobias Knauss Avatar answered Jan 15 '23 04:01

Tobias Knauss