简体   繁体   中英

Force event handler to run on object's thread, C# .NET

I have a class that handles events created by multiple member objects. Events from these objects spawn worker threads for the event, so that the various event handlers in my class are running on different threads (one is a serial handler, one is a timer event, etc.) I'm looking for a simple way to make my code thread-safe, preferably by forcing the event handlers to run on my object's thread.

If this were a Forms UI object, I could take advantage of its implementation of the ISynchronizeInvoke interface, and make calls to InvokeRequired , Invoke , etc. In WPF I could use a Dispatcher object. But my class needs to run *independently of any UI code.

Here's a simplified example of what I have:

public class MyClass
{
    private SomeObject object1;
    private AnotherObject object2;

    public MyClass()
    {
        object1 = new SomeObject();
        object2 = new AnotherObject();

        object1.AThreadedEvent += ThreadedEventHandler1;
        object2.AnotherThreadedEvent += ThreadedEventHandler2;
    }

    // This runs in its own thread!
    private void ThreadedEventHandler1()
    {
        // DO STUFF HERE
    }

    // This runs in its own thread!
    private void ThreadedEventHandler2()
    {
        // DO STUFF HERE
    }
}

Because both event handlers access the same objects in the parent class (including each-other!), it would be awesome if there were a simple way to force the event handlers to run in the creating object's thread.

I've toyed with the idea of having my class implement the ISynchronizeInvoke interface, but it appears that doing so can get pretty complicated. Before I jump down that rabbit hole, I thought I'd ping the experts to see if there is a more simple solution.

Thoughts?

EDIT:

Part of the reason I want to run the event handlers in the parent object's thread is because the parent object has it's *own events that are triggered based on the events sent by its member objects. I'd like any threading functionality to be hidden by this class, so that code that uses the class doesn't have to worry about thread-related issues (ie. locks and so on). Simply locking shared data won't do the job, because I *still need to trigger events from within the threaded event handlers.

The ideea of invoking on another thread is hand to hand with having a while loop that from time to time it checks whether there is an "outside" message to be processed. For UI, there is the windows loop that does that. For an external thread, you must write manually a loop. Imagine a situation without a loop and that you have a relative long running thread right ? and sudently you want to interrupt this thread to invoke your message and resume what it was doing ON THE SAME shared stack memory. This interruption would destroy your stack. This is simply NOT possible. The other possibility is to use a synchronization mechanism such as ManualResetEvent and just wait for a signal (a signal that comes outside your thread). So, to resume, in order to process a message from another thread, you basically have only two options:

1) You have a while loop, eventually using a little sleep (to give some time / ticks to other threads to do their job)

while (true) {
  Thread.Sleep (5);
  if (someMessageArrived) { ... }
}

2) You just wait for a message implementing somehow the producer / consummer architecture:

On listening thread:
aManualResetEvent.WaitOne ();

On the "producer" thread:
aManualResetEvent.Set ();

There are advanced classes in .NET framework that might help such as BlockingCollection.

Hope this helps

Assumming, that your class runs in its own thread that the only logic is to execute the incomming calls from other threads, this would be the solution: (comments inside)

public class MyClass
{
    private SomeObject object1;
    private AnotherObject object2;

    public MyClass()
    {
        object1 = new SomeObject();
        object2 = new AnotherObject();

        object1.AThreadedEvent += ThreadedEventHandler1;
        object2.AnotherThreadedEvent += ThreadedEventHandler2;
    }

    // This runs in its own thread!
    // Only add the real function call to the queue
    public void ThreadedEventHandler1()
    {
        tasks.Add(ThreadedEventHandler1_really);
    }

    private void ThreadedEventHandler1_really()
    {
        // DO STUFF HERE
    }

    // This runs in its own thread!
    // Only add the real function call to the queue
    public void ThreadedEventHandler2()
    {
        tasks.Add(ThreadedEventHandler2_really);
    }

    // here is the actual logic of your function
    private void ThreadedEventHandler2_really()
    {
        // DO STUFF HERE
    }

    // the queue of the tasks
    BlockingCollection<Action> tasks = new BlockingCollection<Action>();

    // this method never returns, it is blocked forever 
    // and the only purpose of i is to do the functions calls when they added to the queue
    // it is done in the thread of this instance
    public void StartConsume()
    {
        foreach (Action action in tasks.GetConsumingEnumerable())
        {
            // add logic before call
            action();
            // add logic after call
        }
    }
}

The solution based on that the caller threads tat calls the functions: ThreadedEventHandler1 and ThreadedEventHandler2, actually add the real call to the queue and emediately continue with their run.

From the other hand, StartConsume function iterates the queue and makes the calls of the added method calls. If you want to add another logic before and after call, you can add it in this function.

Hope it helped to achieve your goal.

Without completely understanding the rational behind your design. I can say that the problem you are trying to solve was solved many times before.

I will assume your main object is like a service which expects calls (in this case events) from itself and other services (the sub objects). If you would think about it in terms of services (which you arguably should) WCF solves that problem for you doing all the heavy lifting @Rami suggested.

You define the main service with the following behavior:
Instance Context Mode - Single
Concurrency Mode - Single
More about these here .

And every event handler would call that main service notifying it about the event.

I am pretty sure you would not go that far and implement every class as a service , but thought it is worth offering anyway as an option.

OK, based on all of your feedback (thanks!) I have a solution to my problem. The short answer: what I wanted to do isn't possible.

Here are more details for those who asked. I'm writing a DLL that manages a device attached to a serial port. This includes basic serial port COM (packet TX and RX, including parsing), and higher-level protocol behavior (TX, Ack, retry on timeout, etc.) The serial port event handlers provided by .NET are obviously asynchronous, as are the System.Timers.Timer objects that I use to handle timeouts, etc.

I am building the code around an MVVM architecture, so that my UI doesn't have any logic in it whatsoever. Hence my need to avoid exploiting Dispatcher or Invoke functionality provided by the UI.

What I was looking for was a way to handle asynchronous events within my DLL in the same simple manner provided by WinForms and WPF. But as has been pointed out, and as I learned when digging deeper, what you are *really doing when you call BeginInvoke or a Dispatcher is pushing something onto a queue, to be consumed later by a different thread polling the queue. Outside the context of a UI, no such polling architecture exists.

SO. My options are to lock the shared objects in my class to make it thread safe, or to implement my own polling architecture within another thread (to avoid blocking the program that uses the DLL) that emulates what the UI code already does.

In either case, the UI code will still need to use its Invoke or equivalent tools when handling events from the DLL class. I suppose that's OK.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM