How to block until an event is fired in c#
Asked Answered
M

4

79

After asking this question, I am wondering if it is possible to wait for an event to be fired, and then get the event data and return part of it. Sort of like this:

private event MyEventHandler event;
public string ReadLine(){ return event.waitForValue().Message; }
...
event("My String");
...elsewhere...
var resp = ReadLine();

Please make sure whatever solution you provide returns the value directly rather than getting it from something else. I'm asking if the method above is available in some way. I know about Auto/ManuelResetEvent, but I don't know that they return the value directly like I did above.

Update: I declared an event using MyEventHandler (which contains a Message field). I have a method in another thread called ReadLine waiting for the event to fire. When the event fires the WaitForValue method (part of the event handling scene) returns the event args, which contains the message. The message is then returned by ReadLine to whatever had called it.

The accepted answer to that question I asked was what I did, but it just doesn't feel quite right. It almost feels like something could happen to the data between the ManuelResetEvent firing and the program retrieving the data and returning it.

Update: The main problem with the Auto/ManualResetEvent is that it is too vulnerable. A thread could wait for the event, and then not give enough time for anyone else to get it before changing it to something else. Is there a way to use locks or something else? Maybe using get and set statements.

Martz answered 5/10, 2012 at 12:1 Comment(12)
what about a while loop: while (someGlobalvar); cahnge someGlobalvar in a function you asign to the event.Leilanileininger
it's active waiting, and it's a very bad solutionAthens
Now, 2 years later, it seems like I should just make WaitTwo which would return an object, and *ResetEvent.Set(object data). I would still need to worry about object references, but that is normal. At least the pointer wouldn't change.Martz
Possible duplicate of Wait inside method until event is capturedFilicide
Except that this question predates that one :)Martz
@ArlenBeiler Duplicity is two-way relation ship IMHO, if A is a duplicity of B, then B is duplicity of A, so I don't see how pre-dating is relevant.Filicide
How is the ManualResentEvent is vulnerable? I would assume that the implementation is simply a CreateEvent() and a WaitForSingleObject() which blocks until the event is signaled. Given your update, I would suggest that you learn about multi-threaded applications and get a better understanding of how it all works.Simplehearted
@ArlenBeiler Hi Arien, were you able to find a solution for this? It seems what you want is something similar to Console.ReadLine(), correct? The code flow stops there until something is entered. I have the same problem and would appreciate any help.Gautier
@Vahid, With 7 more years of programming experience since I asked this question, I would say that if you have to do this, you're probably doing it wrong. It's better to specify a callback. If you're working with old systems that don't allow anything else, such as COM, then just have code wait on a ManualResetEvent and set a second one to protect writing until all the threads are done reading, I guess.Martz
I would also recommend you check out reactivex.io.Martz
@ArlenBeiler Thanks Arlen, please consider this : My code asks the user for a click, waits till he clicks proceeds to next line and asks for another click! All the while the UI is responsive and even a Line is drawn as the user moves the mouse. Of course I can subscribe to MouseUp and MouseMove events in my UI and do this, but this seems to get more complex easily. Wouldn't it be easier to write it as p1 = PickPoint(); p2 = PickPoint() and wait between the calls? Or is there a third way to to this?Gautier
In that case a ManuelResetEvent will work fine. Please see https://mcmap.net/q/262348/-how-to-block-until-an-event-is-fired-in-cMartz
A
66

If the current method is async then you can use TaskCompletionSource. Create a field that the event handler and the current method can access.

    TaskCompletionSource<bool> tcs = null;

    private async void Button_Click(object sender, RoutedEventArgs e)
    {
        tcs = new TaskCompletionSource<bool>();
        await tcs.Task;
        WelcomeTitle.Text = "Finished work";
    }

    private void Button_Click2(object sender, RoutedEventArgs e)
    {
        tcs?.TrySetResult(true);
    }

This example uses a form that has a textblock named WelcomeTitle and two buttons. When the first button is clicked it starts the click event but stops at the await line. When the second button is clicked the task is completed and the WelcomeTitle text is updated. If you want to timeout as well then change

await tcs.Task;

to

await Task.WhenAny(tcs.Task, Task.Delay(25000));
if (tcs.Task.IsCompleted)
    WelcomeTitle.Text = "Task Completed";
else
    WelcomeTitle.Text = "Task Timed Out";
Awakening answered 6/7, 2016 at 14:0 Comment(8)
You don't need async to use the TaskCompletionSourceDelorisdelorme
That's true, but you do need it if you want to await the Task without blocking the UI.Awakening
Can a TaskCompletionSource be reset?Array
@KyleDelaney No.In my example you would replace the tcs with a new instanceAwakening
How can you make this with event that is not a button click? Like is there a way to invoke an event wait for the handler to finish executing and then at the end call TrySetResult(true)?Curvet
@Awakening Thank you, this is awesome. And even more thanks for the waiting strategy. Is assignment to the task completion source something like subscribing to an event?Hymn
What is the purpose of assigning new TaskCompletionSource<bool>() inside the ButtonClick and not directly in the field?Hymn
@Hymn you can't replace tcs every time you want to access it, but you need to replace it after it has been used (because it cannot be reset)Awakening
E
47

You can use ManualResetEvent. Reset the event before you fire secondary thread and then use the WaitOne() method to block the current thread. You can then have secondary thread set the ManualResetEvent which would cause the main thread to continue. Something like this:

ManualResetEvent oSignalEvent = new ManualResetEvent(false);

void SecondThread(){
    //DoStuff
    oSignalEvent.Set();
}

void Main(){
    //DoStuff
    //Call second thread
    System.Threading.Thread oSecondThread = new System.Threading.Thread(SecondThread);
    oSecondThread.Start();

    oSignalEvent.WaitOne(); //This thread will block here until the reset event is sent.
    oSignalEvent.Reset();
    //Do more stuff
}
Editorial answered 5/10, 2012 at 12:10 Comment(1)
This doesn't return the value directly. I already tried that in the other question. Isn't there anything like I'm asking about?Martz
N
4

A very easy kind of event you can wait for is the ManualResetEvent, and even better, the ManualResetEventSlim.

They have a WaitOne() method that does exactly that. You can wait forever, or set a timeout, or a "cancellation token" which is a way for you to decide to stop waiting for the event (if you want to cancel your work, or your app is asked to exit).

You fire them calling Set().

Here is the doc.

Notornis answered 5/10, 2012 at 12:12 Comment(2)
But that doesn't return any kind of value.Martz
Can you edit the qu and add an overview of the different components and their interactions? It will help us to see better what you are trying to achieve and hopefully lead to an improved design.Mantilla
I
1

If you're happy to use the Microsoft Reactive Extensions, then this can work nicely:

public class Foo
{
    public delegate void MyEventHandler(object source, MessageEventArgs args);
    public event MyEventHandler _event;
    public string ReadLine()
    {
        return Observable
            .FromEventPattern<MyEventHandler, MessageEventArgs>(
                h => this._event += h,
                h => this._event -= h)
            .Select(ep => ep.EventArgs.Message)
            .First();
    }
    public void SendLine(string message)
    {
        _event(this, new MessageEventArgs() { Message = message });
    }
}

public class MessageEventArgs : EventArgs
{
    public string Message;
}

I can use it like this:

var foo = new Foo();

ThreadPoolScheduler.Instance
    .Schedule(
        TimeSpan.FromSeconds(5.0),
        () => foo.SendLine("Bar!"));

var resp = foo.ReadLine();

Console.WriteLine(resp);

I needed to call the SendLine message on a different thread to avoid locking, but this code shows that it works as expected.

Invasion answered 5/10, 2012 at 12:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.