Invoke a delegate on a specific thread C#
Asked Answered
R

6

10

Is there any way to get a delegate to run on a specific thread?

Say I have:

CustomDelegate del = someObject.someFunction;
Thread dedicatedThread = ThreadList[x];

Can I have a consistent background long running thread and invoke my own delegates on it whenever I need it. It has to be the same thread each time.

[Edit]

The reason why I want it to be on a dedicated thread is time is that I inten to run the delegate on it and suspend the thread after y milliseconds, and resume the thread when I run another delegate on it. I see this isn't possible. I will have a delegate queue and let the main function of the thread read and run from it.

To clarify with a concrete example, I have a game system with a bunch of player threads. I would like each playerthread to run event handlers for game events on it. If the event handlers take too much time I would like to be able to pause that particular player until the next event by suspending its thread.

Thus having a dedicated thread on which I can run multiple event handlers I can pause a particular player's AI in case it became bugged or is taking too long.

Rockabilly answered 13/8, 2010 at 22:26 Comment(0)
D
3

I think the best solution is to use Task objects and queue them to an StaThreadScheduler running a single thread.

Alternatively, you could use the ActionThread in Nito.Async to create a normal thread with a built-in queue of Action delegates.

However, neither of these will directly address another need: the ability to "pause" one action and continue with another one. To do this, you'd need to sprinkle "synchronization points" throughout every action and have a way to save its state, re-queue it, and continue with the next action.

All that complexity is very nearly approaching a thread scheduling system, so I recommend taking a step back and doing more of a re-design. You could allow each action to be queued to the ThreadPool (I recommend just having each one be a Task object). You'll still need to sprinkle "synchronization points", but instead of saving state and re-queueing them, you'll just need to pause (block) them.

Durative answered 14/8, 2010 at 1:31 Comment(2)
Thank you for pointing out the ActionThread, that looks very useful. I needed to encapsulate external code over which I have little control in a thread. All I have is delegates to the external code. If I can insure that the object is in one particular thread I can "pause" that object by just suspending the thread. I don't think the threadpool will allow me to "pause" the object nicely.Rockabilly
A word of warning: it is possible (even likely) to cause deadlocks accidentally by suspending/resuming threads. If at all possible, change the external code. If that isn't possible, then consider using thread priorities instead of suspend/resume.Durative
R
2

Normally, I would suggest just using a the thread pool or BackgroundWorker class - but these do not guarantee that work will occur on any particular thread. It's unclear why you care which thread runs the works, but assuming that it does somehow matter ...

You would have to pass the Delegate object through some kind of shared memory, like a queue. The background thread would have to watch this queue, pull delegates off of it when they exist, and execute them.

If it turns out that the thread pool is acceptable to run your code, you could always use the BeginInvoke method of the delegate to do so:

// wrap your custom delegate in an action for simplicity ...
Action someCode = () => yourCustomDelegate( p1, p2, p3, ... );
// asynchronously execute the currying Action delegate on the threadpool...
someCode.BeginInvoke( someCode.EndInvoke, action );
Rosannarosanne answered 13/8, 2010 at 22:32 Comment(0)
E
2

Unfortunately there really is nothing built in to do this on any generic thread. You can accomplish this by creating a class that wraps a Thread and implements ISynchonizeInvoke.

A simple approach is to create an event processing queue on the dedicated thread as LBushkin mentions. I suggest using a Queue<Action> class and calling the Action delegate directly. You can accomplish most tasks that you would need using anonymous delegate actions.

Finally, just as a word of warning, I would suggest you use a Semaphore or EventWaitHandle instead of Thread.Sleep on your dedicated thread. It is definitely more friendly than executing your background loop over and over again when its unnecessary.

Elnaelnar answered 13/8, 2010 at 22:36 Comment(2)
Thank you, I was intending to run the background loop over and over. I will look into an event wait handlerRockabilly
I agree with the warning about Thread.Sleep; however, the op should implement SynchronizationContext rather than the outdated ISynchronizeInvoke.Durative
C
1

A pattern with synchronization points. One feature of this is that the worker thread can be started before or after the blocking calls for work are made. This was for a COM object wrapper called Watin for manipulating instances of Internet Explorer which is, like a wee crying baby, extremely context sensitive. Improvement might be to remove the Thread.Sleep(), possibly with an AutoResetEvent, which would drastically improve performance in certain situations.

The idea for this pattern was from Justin Breitfeller's, Stephen Cleary's, and LBushkin's answers.

    private Instantiate()
    {
        BrowserQueue = new ConcurrentQueue<BrowserAction>();
        BrowserThread = new Thread(new ThreadStart(BrowserThreadLoop));
        BrowserThread.SetApartmentState(ApartmentState.STA);
        BrowserThread.Name = "BrowserThread";
        BrowserThread.Start();
    }
    private static void BrowserThreadLoop()
    {
        while (true)
        {
            Thread.Sleep(500);
            BrowserAction act = null;
            while (Instance.BrowserQueue.TryDequeue(out act))
            {
                try
                {
                    act.Action();
                }
                catch (Exception ex) { }
                finally
                {
                    act.CompletionToken.Set();
                }
            }
        }
    }
    public static void RunBrowserAction(Action act)
    {
        BrowserAction ba = new BrowserAction() { Action = act, CompletionToken = new ManualResetEvent(false) };
        Instance.BrowserQueue.Enqueue(ba);
        ba.CompletionToken.WaitOne();
    }
    public class BrowserAction
    {
        public Action Action { get; set; } = null;
        public ManualResetEvent CompletionToken { get; set; } = null;
    }
    ConcurrentQueue<BrowserAction> BrowserQueue;
Candlestand answered 12/8, 2018 at 18:12 Comment(0)
P
0

For threads that you create, you can only specify the ThreadStart delegate when you create them. There's no provision for injecting a different delegate into a created thread. The Thread Pool is different in that it allows you to submit delegates to previously created threads that it starts on your behalf.

It's not clear what problem you're trying to solve. What are you trying to accomplish (or avoid) by trying to run multiple delegates on one thread?

Parsifal answered 13/8, 2010 at 22:55 Comment(0)
C
0

Microsoft's Reactive Framework has the scheduler EventLoopScheduler which creates a thread and hangs on to it until you dispose of the instance. You can then run delegate on the scheduler and hence the same thread.

using (var els = new EventLoopScheduler())
{
    els.Schedule(() =>
    {
        Console.WriteLine("On the thread");
    });

    els.Schedule(() =>
    {
        Console.WriteLine("Still on the same thread");
    });
    Console.ReadLine();
}

There are also a bunch of other interesting overloads:

using (var els = new EventLoopScheduler())
{
    int i = 0;
    els.Schedule(reschedule =>
    {
        Console.WriteLine(i);
        if (i++ < 10)
        {
            reschedule();
        }
    });
    Console.ReadLine();
}

As well as timer-like functions.

Corner answered 15/10, 2023 at 12:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.