How to block code flow until an event is fired in C#
Asked Answered
A

7

11

I have a grid with a button in a WPF application. When the user clicks the button, a method in a utility class is executed which forces the application to receive a click on the grid. The code flow must stop here and not continue until the user has clicked on the grid.

I had a similar question before and I got an answer using async/await, but since I am using this method as part of an API, I do not want to use async/await because it will require the consumers of the API to mark their methods as async, which I do not want.

Wait till user click C# WPF

How can I write the Utility.PickPoint(Grid grid) method to achieve this goal without using async/await? I saw this answer which may be helpful, but I did not fully understand how to apply it to my situation.

Blocking until an event completes

Consider it like the Console.ReadKey() method in a console application. When we call this method, the code flow stops until we enter some value. The debugger does not continue until we enter something. I want the exact behavior for the PickPoint() method. The code flow will stop until the user clicks on the grid.

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="3*"/>
            <RowDefinition Height="1*"/>
        </Grid.RowDefinitions>

        <Grid x:Name="View" Background="Green"/>
        <Button Grid.Row="1" Content="Pick" Click="ButtonBase_OnClick"/>
    </Grid>
</Window>

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
    {
        // do not continue the code flow until the user has clicked on the grid. 
        // so when we debug, the code flow will literally stop here.
        var point = Utility.PickPoint(View);


        MessageBox.Show(point.ToString());
    }
}

public static class Utility
{
    public static Point PickPoint(Grid grid)
    {

    }
}
Ape answered 13/4, 2020 at 11:12 Comment(8)
Obvious way is Aync/Await how about doing Operation A and saving that operation STATE now you want that user should click Grid ..so if user Clicks Grid you check the state if true then do your operation else just do whatever you want to ??Breannabreanne
@RaoHammasHussain I updated my question with a link that may help. The utility method will be part of an API that the user of the API will call whenever he wants to request the end user to click on the screen. Consider it like a prompt window for text in normal windows applications or Console.Readline() method. In these cases, the code flow stops until the user enters something. Now I want the exact thing but this time the user clicks on the screen.Ape
AutoResetEvent is not what you want ?Breannabreanne
@RaoHammasHussain I think so but really do not know how to use it here.Ape
It's like you are intentionally implementing WAIT STATE. is it really required ? cuz can't you just put this var point = Utility.PickPoint(Grid grid); in Grid Click method ? do some operation and return the response ?Breannabreanne
what you seem to be doing is first User clicks some button and Wait State is ON now if user clicks Grid then you'll perform some operation the you want to be back at wait state position ? i know it could be your requirement but what if user don't click grid ? then ?Breannabreanne
@RaoHammasHussain If the user does not click it will remain there, or we can bind pressing the Esc key to exit it and continue to the next line of code.Ape
You should define "the code flow". WPF is event driven, there is no flow.Sachiko
N
11

"How to block code flow until an event is fired?"

Your approach is wrong. Event-driven does not mean blocking and waiting for an event. You never wait, at least you always try hard to avoid it. Waiting is wasting resources, blocking threads and maybe introducing the risk of a deadlock or zombie thread (in case the release signal is never raised).
It should be clear that blocking a thread to wait for an event is an anti-pattern as it contradicts the idea of an event.

You generally have two (modern) options: implement an asynchronous API or an event-driven API. Since you don't want to implement your API asynchronous, you are left with the event-driven API.

The key of an event-driven API is, that instead of forcing the caller to synchronously wait for a result or poll for a result, you let the caller continue and send him a notification, once the result is ready or the operation has completed. Meanwhile, the caller can continue to execute other operations.

When looking at the problem from a threading perspective, then the event-driven API allows the calling thread e.g., the UI thread, which executes the button's event handler, to be free to continue to handle e.g. other UI related operations, like rendering UI elements or handling user input like mouse movement and key presses. The event-driven API has the same effect or goal like an asynchronous API, although it is far less convenient.

Since you didn't provide enough details on what you are really trying to do, what Utility.PickPoint() is actually doing and what the result of the task is or why the user has to click on the `Grid, I can't offer you a better solution. I just can offer a general pattern of how to implement your requirement.

Your flow or the goal is obviously divided into at least two steps to make it a sequence of operations:

  1. Execute operation 1, when the user clicks the button
  2. Execute operation 2 (continue/complete operation 1), when the user clicks on the Grid

with at least two constraints:

  1. Optional: the sequence must be completed before the API client is allowed to repeat it. A sequence is completed once operation 2 has run to completion.
  2. Operation 1 is always executed before operation 2. Operation 1 starts the sequence.
  3. Operation 1 must complete before the API client is allowed to execute operation 2

This requires at two notifications (events) for the client of the API to allow non-blocking interaction:

  1. Operation 1 completed (or interaction required)
  2. Operation 2 (or goal) completed

You should let your API implement this behavior and constraints by exposing two public methods and two public events.

Since this implementation only allows a single (non-concurrent) call to the API it's also recommended to expose a IsBusy property to indicate a running sequence. This allows polling the current state before starting a new sequence, although it is recommended to wait for the completed event to execute subsequent calls.

Implement/refactor Utility API

Utility.cs

class Utility
{
  public event EventHandler InitializePickPointCompleted;
  public event EventHandler<PickPointCompletedEventArgs> PickPointCompleted;
  public bool IsBusy { get; set; }
  private bool IsPickPointInitialized { get; set; }

  // The prefix 'Begin' signals the caller or client of the API, 
  // that he also has to end the sequence explicitly
  public void BeginPickPoint(param)
  {
    // Implement constraint 1
    if (this.IsBusy)
    {
      // Alternatively just return or use Try-do pattern
      throw new InvalidOperationException("BeginPickPoint is already executing. Call EndPickPoint before starting another sequence.");
    }

    // Set the flag that a current sequence is in progress
    this.IsBusy = true;

    // Execute operation until caller interaction is required.
    // Execute in background thread to allow API caller to proceed with execution.
    Task.Run(() => StartOperationNonBlocking(param));
  }

  public void EndPickPoint(param)
  {
    // Implement constraint 2 and 3
    if (!this.IsPickPointInitialized)
    {
      // Alternatively just return or use Try-do pattern
      throw new InvalidOperationException("BeginPickPoint must have completed execution before calling EndPickPoint.");
    }

    // Execute operation until caller interaction is required.
    // Execute in background thread to allow API caller to proceed with execution.
    Task.Run(() => CompleteOperationNonBlocking(param));
  }

  private void StartOperationNonBlocking(param)
  {
    ... // Do something

    // Flag the completion of the first step of the sequence (to guarantee constraint 2)
    this.IsPickPointInitialized = true;

    // Request caller interaction to kick off EndPickPoint() execution
    OnInitializePickPointCompleted();
  }

  private void CompleteOperationNonBlocking(param)
  {
    // Execute goal and get the result of the completed task
    Point result = ExecuteGoal();

    // Reset API sequence (allow next client invocation)
    this.IsBusy = false;
    this.IsPickPointInitialized = false;

    // Notify caller that execution has completed and the result is available
    OnPickPointCompleted(result);
  }

  private void OnInitializePickPointCompleted()
  {
    // Set the result of the task
    this.InitializePickPointCompleted?.Invoke(this, EventArgs.Empty);
  }

  private void OnPickPointCompleted(Point result)
  {
    // Set the result of the task
    this.PickPointCompleted?.Invoke(this, new PickPointCompletedEventArgs(result));
  }
}

PickPointCompletedEventArgs.cs

class PickPointCompletedEventArgs : AsyncCompletedEventArgs 
{
  public Point Result { get; }

  public PickPointCompletedEventArgs(Point result)
  {
    this.Result = result;
  }
}

Use the API

MainWindow.xaml.cs

partial class MainWindow : Window
{
  private Utility Api { get; set; }

  public MainWindow()
  {
    InitializeComponent();

    this.Api = new Utility();
  }

  private void StartPickPoint_OnButtonClick(object sender, RoutedEventArgs e)
  {
    this.Api.InitializePickPointCompleted += RequestUserInput_OnInitializePickPointCompleted;

    // Invoke API and continue to do something until the first step has completed.
    // This is possible because the API will execute the operation on a background thread.
    this.Api.BeginPickPoint();
  }

  private void RequestUserInput_OnInitializePickPointCompleted(object sender, EventArgs e)
  {
    // Cleanup
    this.Api.InitializePickPointCompleted -= RequestUserInput_OnInitializePickPointCompleted;

    // Communicate to the UI user that you are waiting for him to click on the screen
    // e.g. by showing a Popup, dimming the screen or showing a dialog.
    // Once the input is received the input event handler will invoke the API to complete the goal   
    MessageBox.Show("Please click the screen");  
  }

  private void FinishPickPoint_OnGridMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
  {
    this.Api.PickPointCompleted += ShowPoint_OnPickPointCompleted;

    // Invoke API to complete the goal
    // and continue to do something until the last step has completed
    this.Api.EndPickPoint();
  }

  private void ShowPoint_OnPickPointCompleted(object sender, PickPointCompletedEventArgs e)
  {
    // Cleanup
    this.Api.PickPointCompleted -= ShowPoint_OnPickPointCompleted;

    // Get the result from the PickPointCompletedEventArgs instance
    Point point = e.Result;

    // Handle the result
    MessageBox.Show(point.ToString());
  }
}

MainWindow.xaml

<Window>
  <Grid MouseLeftButtonUp="FinishPickPoint_OnGridMouseLeftButtonUp">
    <Button Click="StartPickPoint_OnButtonClick" />
  </Grid>
</Window>

Remarks

Events raised on a background thread will execute their handlers on the same thread. Accessing a DispatcherObject like a UI element from a handler, which is executed on a background thread, requires the critical operation to be enqueued to the Dispatcher using either Dispatcher.Invoke or Dispatcher.InvokeAsync to avoid cross-thread exceptions.
Read the remarks about DispatcherObject to learn more about this phenomenon called dispatcher affinity or thread affinity.
For a convenient usage of the API I suggest to marshall all events to the original context of the caller either by capturing and using the caller's SynchronizationContext or by using AsyncOperation (or the AsyncOperationManager).

The above example can be easily enhanced by providing cancellation (recommended) e.g. by exposing a Cancel() method e.g., PickPointCancel() and progress reporting (preferably using Progress<T>).


Some thoughts - reply to your comments

Because you were approaching me to find a "better" blocking solution, given me the example of console applications, I felt to convince you, that your perception or point of view is totally wrong.

"Consider a Console application with these two lines of code in it.

var str = Console.ReadLine(); 
Console.WriteLine(str);

What happens when you execute the application in debug mode. It will stop at the first line of code and force you to enter a value in Console UI and then after you enter something and press Enter, it will execute the next line and actually print what you entered. I was thinking about exactly the same behavior but in WPF application."

A console application is something totally different. The threading concept is a little different. Console applications don't have a GUI. Just input/output/error streams. You can't compare the architecture of a console application to a rich GUI application. This won't work. You really must understand and accept this.

Also don't get deceived by the looks. Do you know what is happening inside Console.ReadLine? How it is implemented? Is it blocking the main thread and in parallel it reads input? Or is it just polling?
Here is the original implementation of Console.ReadLine:

public virtual String ReadLine() 
{
  StringBuilder sb = new StringBuilder();
  while (true) 
  {
    int ch = Read();
    if (ch == -1) 
      break;
    if (ch == '\r' || ch == '\n') 
    {
      if (ch == '\r' && Peek() == '\n') 
        Read();
      return sb.ToString();
    }
    sb.Append((char)ch);
  }
  if (sb.Length > 0) 
    return sb.ToString();
  return null;
}

As you can see it's a simple synchronous operation. It polls for user input in an "infinite" loop. No magic block and continue.

WPF is build around a rendering thread and a UI thread. Those threads keep always spinning in order to communicate with the OS like handling user input - keeping the application responsive. You never want to pause/block this thread as it will stop the framework from doing essential background work, like responding to mouse events - you don't want the mouse to freeze:

waiting = thread blocking = unresponsiveness = bad UX = annoyed users/customers = trouble in the office.

Sometimes, the application flow requires to wait for input or a routine to complete. But we don't want to block the main thread.
That's why people invented complex asynchronous programming models, to allow waiting without blocking the main thread and without forcing the developer to write complicated and erroneous multithreading code.

Every modern application framework offers asynchronous operations or an asynchronous programming model, to allow the development of simple and efficient code.

The fact that you are trying hard to resist asynchronous programming model, shows some lack of understanding to me. Every modern developer prefers an asynchronous API over a synchronous one. No serious developer cares to use the await keyword or to declare his method async. Nobody. You are the first I encounter who complains about asynchronous APIs and who finds them inconvenient to use.

If I would check your framework, which targets to solve UI related problems or make UI related tasks easier, I would expect it to be asynchronous - all the way.
UI related API which isn't asynchronous is waste, as it will complicate my programming style, therefore my code which therefore becomes more error-prone and difficult to maintain.

A different perspective: when you acknowledge that waiting blocks the UI thread, is creating a very bad and undesirable user experience as the UI will freeze until the waiting is over, now that you realize this, why would you offer an API or plugin model which encourages a developer to do exactly this - implement waiting?
You don't know what the 3rd party plugin will do and how long a routine will take until it completes. This is simply a bad API design. When your API operates on the UI thread then the caller of your API must be able to make non-blocking calls to it.

If you deny the only cheap or graceful solution, then use an event-driven approach as shown in my example.
It does what you want: start a routine - wait for user input - continue execution - accomplish goal.

I really tried several times to explain why waiting/blocking is a bad application design. Again, you can't compare a console UI to a rich graphical UI, where e.g. input handling alone is a multitude more complex than just listening to the input stream. I really don't know your experience level and where you started, but you should start to embrace the asynchronous programming model. I don't know the reason why you try to avoid it. But it's not wise at all.

Today asynchronous programming models are implemented everywhere, on every platform, compiler, every environment, browser, server, desktop, database - everywhere. The event-driven model allows to achieve the same goal, but it's less convenient to use (subscribe/unsubscribe to/from events, read docs (when there are docs) to learn about the events), relying on background threads. Event-driven is old-fashioned and should only be used when asynchronous libraries are not available or not applicable.

As a side-note: the .NET Framwork (.NET Standard) offers the TaskCompletionSource (among other purposes) to provide a simple way to convert an existing even-driven API into an asynchronous API.

"I have seen the exact behavior in Autodesk Revit."

Behavior (what you experience or observe) is much different from how this experience is implemented. Two different things. Your Autodesk is very likely using asynchronous libraries or language features or some other threading mechanism. And it is also context related. When the method that is on your mind is executing on a background thread then the developer may choose to block this thread. He has either a very good reason to do this or just made a bad design choice. You are totally on the wrong track ;) Blocking is not good.
(Is the Autodesk source code open source? Or how do you know how it is implemented?)

I don't want to offend you, please believe me. But please reconsider to implement your API asynchronous. It's only in your head that developers don't like to use async/await. You obviously got the wrong mindset. And forget about that console application argument - it's nonsense ;)

UI related API MUST use async/await whenever possible. Otherwise, you leave all the work to write non-blocking code to the client of your API. You would force me to wrap every call to your API into a background thread. Or to use less comfortable event handling. Believe me - every developer rather decorates his members with async, than doing event handling. Every time you use events you might risk a potential memory leak - depends on some circumstances, but the risk is real and not rare when programming careless.

I really hope you understand why blocking is bad. I really hope you decide to use async/await to write a modern asynchronous API. Nevertheless, I showed you a very common way to wait non-blocking, using events, although I urge you to use async/await.

"The API will allow the programmer to have access to the UI and etc. Now suppose the programmer wants to develop an add-in that when a button is clicked, the final user is asked to pick a point in the UI"

If you don't want to allow the plugin to have direct access to UI elements, you should provide an interface to delegate events or expose internal components via abstracted objects.
The API internally will subscribe to UI events on behalf of the Add-in and then delegates the event by exposing a corresponding "wrapper" event to the API client. Your API must offer some hooks where the Add-in can connect to access specific application components. A plugin API acts like an adapter or facade to give externals access to internals.
To allow a degree of isolation.

Take a look at how Visual Studio manages plugins or allows us to implement them. Pretend you want to write a plugin for Visual Studio and do some research on how to do this. You will realize that Visual Studio exposes its internals via an interface or API. E.G. you can manipulate the code editor or get information about the editor's content without real access to it.

Noontide answered 16/4, 2020 at 21:26 Comment(19)
Hi, thank you for approaching the question from another perspective. Sorry if the question was a bit vague on details. Consider a Console application with these two lines of code in it. var str = Console.ReadLine(); Console.WriteLine(str); What happens when you execute the application in debug mode. It will stop at the first line of code and force you to enter a value in Console UI and then after you enter something and press Enter, it will execute the next line and actually print what you entered. I was thinking about exactly the same behavior but in WPF application.Ape
In the CAD application I am developing, the users are supposed to be able to extend it by addins/plugins. The API will allow the programmer to have access to the UI and etc. Now suppose the programmer wants to develop an addin that when a button is clicked, the final user is asked to pick a point in the UI and then the code will do other cool stuff with the given point. Maybe they will ask for another point to be picked and draw a line, etc.Ape
I have seen the exact behavior in Autodesk Revit.Ape
I have something to say about your requirement. Please read my updated answer. I posted the reply there because it got somehow longer. I admit that you really have triggered me. Please, while reading keep in mind that I don't want to offend you.Noontide
Thank you for your updated answer. Of course, I am not offended. On the contrary, I am really thankful for the time and effort you have placed on this.Ape
Thank you. And do you think you will change your mind? Have you read it all? I may delete it to trim the answer. It is too much. Sorry for that.Noontide
I understand your point about the Console application, but please do not take me wrong. I never wanted to block the UI. I wanted the similar effect await/async had but without using them. For example, with my current implementation using await/async, the code stops at await PickPoint() and would not proceed unless the user actually clicks on the View. This is the current situation and the UI stays responsive! I was trying hard to see if there is something I am missing here! And if some functionality in .Net allows me to drop the await while keeping the UI responsive!Ape
Yes, I read it and I will then copy it in case you want to delete parts of it.Ape
By the way, I had also done this using the events. The funny thing is I had also used the word Operation! But as you said and are totally right, it gets difficult and complicated very fast.Ape
Ah, I see. If you are already using async/await, I highly recommend to leave it like this. I just followed your link "Wait till user click C# WPF". If you implement it like this, using the async Task API, then you already got a neat solution. Now compare this code effort with an event based. Instead of subscribing to an event, handling it and unsubscribing from it, you just call a method using await. And when looking at the final code, every developer instantly knows that there are some asynchronous calls made. Also every user of the API knows that the API won't block. Very transparent.Noontide
Also note the subtle difference: when using await the code is not stopping. Stopping means blocking the thread. But it doesn't. The instruction pointer just leaves the scope and continues executing other UI thread related operations.Noontide
Or in other words, await returns immediately from the current method returning a Task object. You know that when a method returns, the calling scope continues to execute. The returned Task object stores the method's remaining instructions, that follow the await - for the future. Meanwhile, when the awaited method has completed, the current execution will pause and the previously returned Task object will continue to execute the remaining code (the left over code after the await). That's the magic. No stopping or waiting. Always busy.Noontide
So you are not missing anything at all. You already implemented the best solution. I don't know the details, but as long you are using the Task API you are at least on the right track.Noontide
Yes. Exactly. I am using the Task API. Thank you again for patiently discussing this. I will keep this open for another 5 days till the bounty expires. And if no other meaningful answer comes, will chose yours as it best explains the limitations.Ape
It should not happen with your code, but sometimes we all make errors so when subscribing to events I like to unsubscribe to it first to avoid double subscription to the event in case it was already subscribed (unsubscribing an event that was not subscribed causes no error).Faubion
@bradbury9 You are absolutely right. That is what I meant when saying event driven is less convenient than asynchronous. Events introduce the risk of a memory leak. Unsubscribing before subscribing is one extra measure you can take to minimize this risk, but it doesn't eliminate it, since the true nature of the potential memory leaks are object lifecycles. But you can clearly see the code bloat when handling events and trying to handle them cleanly. Don't forget, the event delegate also introduces a race condition (you may try to execute removed handlers or skip freshly attached handlers).Noontide
"the event delegate also introduces a race condition (you may try to execute removed handlers or skip freshly attached handlers)" I thought it was avoided if it was called using the ? operator to call the delegate. Avoid race condition ?. operator. Fully agree with the rest of the commentFaubion
@bradbury9 Yes, you are correct. But this is another extra measure you have to take to eliminate the race condition (the "new" Null-conditional operator has made it syntactically more simple or concise). That's exactly what I meant: without this extra measures event handling got some pitfalls. So you need extra code and thoughts to omit them. This reduces convenience, especially when compared to the sleek async/await.Noontide
@Ape If you still interested, take a look at the Some thoughts - reply to your comments section - the beginning. I've posted the original implementation of` Console.ReadLineto show you that there is no magic going on. In fact it is a simple while(true) loop to poll the input. There is no blocking and continue. Under the hoodConsole.ReadLinekeeps executing and executing until an abort condition is met. I read through my post and recognized once more how impressed and intrigued you were by theConsole.ReadLinebehavior. When reviewing the implementation you should lose the last doubtsNoontide
S
5

I personally think this is being over-complicated by everyone, but maybe I am not fully understanding the reason why this needs to be done a certain way, but it seems like a simple bool check can be used here.

First and foremost, make your grid hit-testable by setting the Background and IsHitTestVisible properties, or else it won't even capture mouse clicks.

<grid MouseLeftButtonUp="Grid_MouseLeftButtonUp" IsHitTestVisible="True" Background="Transparent">

Next create a bool value that can store whether the "GridClick" event should occur. When the grid is clicked, check that value and the perform execution from the grid click event if it's awaiting the click.

Example:

bool awaitingClick = false;


private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
   awaitingClick=true;
}

private void Grid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{     
     //Stop here if the program shouldn't do anything when grid is clicked
     if (!awaitingClick) { return; } 

     //Run event
     var point = Utility.PickPoint(View);
     MessageBox.Show(point.ToString());

     awaitingClick=false;//Reset
}
Someone answered 15/4, 2020 at 20:56 Comment(4)
Hi Tronald, I think you have misunderstood the question. What I need is for the code to stop at Utility.PickPoint(View) and continue only after the user has clicked on Grid.Ape
Oh yep, I totally misunderstood. My apologies, I didn't realize you needed everything to actually stop. I don't think that's possible without multi-threading as the entire UI will be blocked.Someone
I am still not sure if it is not possible. It is definitely possible with async/await which is not a multi-threaded solution. But what I need is an alternative to the async/await solution.Ape
Sure, but you mentioned that you can't use async/await. It seems like you would need to make use of a dispatcher and a thread that's separated from the main thread (which executes on the UI). I hope you find another way though as I am interested myselfSomeone
B
2

I tried a few things but i'm unable to make it without async/await. Because if we don't use it it causes DeadLock or UI is Blocked and then we are enable to take Grid_Click input.

private async void ToolBtn_OnClick(object sender, RoutedEventArgs e)
{
    var senderBtn = sender as Button;
    senderBtn.IsEnabled = false;

    var response = await Utility.PickPoint(myGrid);
    MessageBox.Show(response.ToString());
    senderBtn.IsEnabled = true;
}  

public static class Utility
{
    private static TaskCompletionSource<bool> tcs;
    private static Point _point = new Point();

    public static async Task<Point> PickPoint(Grid grid)
    {
        tcs = new TaskCompletionSource<bool>();
        _point = new Point();

        grid.MouseLeftButtonUp += GridOnMouseLeftButtonUp;


        await tcs.Task;

        grid.MouseLeftButtonUp -= GridOnMouseLeftButtonUp;
        return _point;
    }


    private static void GridOnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {

        // do something here ....
        _point = new Point { X = 23, Y = 34 };
        // do something here ....

        tcs.SetResult(true); // as soon its set it will go back

    }
}
Breannabreanne answered 13/4, 2020 at 14:33 Comment(2)
@thanks, this is the same answer I got for my other question which uses async / await.Ape
oh yes ! i noticed it now but i guess it's the only way i found that's workingBreannabreanne
S
2

You could block asynchronously using a SemaphoreSlim:

public partial class MainWindow : Window, IDisposable
{
    private readonly SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(0, 1);

    public MainWindow()
    {
        InitializeComponent();
    }

    private async void ButtonBase_OnClick(object sender, RoutedEventArgs e)
    {
        var point = Utility.PickPoint(View);

        // do not continue the code flow until the user has clicked on the grid. 
        // so when we debug, the code flow will literally stop here.
        await _semaphoreSlim.WaitAsync();

        MessageBox.Show(point.ToString());
    }

    private void View_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        //click on grid detected....
        _semaphoreSlim.Release();
    }

    protected override void OnClosed(EventArgs e)
    {
        base.OnClosed(e);
        Dispose();
    }

    public void Dispose() => _semaphoreSlim.Dispose();
}

You cannot, and you don't want to either, block the dispatcher thread synchronously because then it will never be able to handle the click on the Grid, i.e. it cannot both be blocked and handle events at the same time.

Soybean answered 14/4, 2020 at 12:34 Comment(9)
Thanks for an alternative answer. I wonder how it is done for example in Console.Readline()? When you reach this method in debugger, it magically stops there unless we enter something? Is it fundamentally different in Console applications? Can't we have the same behavior in WinForms/WPF application? I have seen this in Autodesk Revit's API, there is a PickPoint() method there that forces you to pick a point on the screen and I have not seen any async / await used! Is it at least possible to hide the await keyword and call it somehow from a sync method?Ape
@Vahid: Console.Readline blocks, i.e. it doesn't return until a line has been read. Your PickPoint method does not. It returns immediately. It could potentially block but then you won't be able to handle UI input during the meantime as I wrote in my answer. In other words, you would have to handle the click inside the method to get the same behaviour.Soybean
The Console.Realine() blocks but at the same time KeyPress events are allowed. Can't we have the exact same behavior here? Blocking by PickPoint() and only allowing MouseEvents? I cannot grasp why it is possible in Console but not on a UI based application .Ape
Then you need to setup a separate dispatcher in PickPoint that handles the mouse events. I fail to see where you are going with this?Soybean
I will try to explain it. Say I have an application that has Grid in it. This application has a public API that allows its users to extend its capabilities. One of the methods in the API is PickPoint(). When the user of the API uses this in their code, the code does not continue unless the end user of the application clicks on the Grid. After clicking it we do other stuff with this Point. Now I need to stop the code flow here and force the user to pick a point. I do not see how else it would be possible? I have seen the exact same thing in Autodesk Revit's public API.Ape
@Vahind: Make the code async and let the user await the method then? This is the API I would expect as a UI developer. Calling a blocking method in a UI application doesn't make any sense.Soybean
I have already done that. It was actually answered for the other question I linked to in my question using async / await! And it is working, I was really curious if it is possible ever without async / await? In Revit's API I do not see any async / await. I can normally call it from a non-async method. Or if it is possible to hide the async / await implementation here to make it possible to call from a non-async method. I do not want to force the final user of the API to write async methods just to be able to use the PickPoint() method.Ape
@Vahid: I don't know what Revit is or how it's implemented. But tou do want the method to have an async API to let the caller know that it's not blocking. "Hiding" this makes even less sense.Soybean
@It is just a CAD application. The point is it seems it is possible with some trickery.Ape
L
2

Technically it is possible with AutoResetEvent and without async/await, but there is a significant drawback:

public static Point PickPoint(Grid grid)
{
    var pointPicked = new AutoResetEvent(false);
    grid.MouseLeftButtonUp += (s, e) => 
    {
        // do whatever after the grid is clicked

        // signal the end of waiting
        pointPicked.Set();
    };

    // code flow will stop here and wait until the grid is clicked
    pointPicked.WaitOne();
    // return something...
}

The drawback: If you call this method directly in a button event handler as your sample code does, deadlocking will occur and you will see that the application stops responding. Because you are using the only UI thread for waiting the user's click, it cannot respond to any user's action, including the user's click on the grid.

Consumers of the method should invoke it in another thread to prevent deadlocks. If it can be guaranteed, that's fine. Otherwise, you need to invoke the method like this:

private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
    // here I used ThreadPool, but you may use other means to run on another thread
    ThreadPool.QueueUserWorkItem(new WaitCallback(Capture));
}

private void Capture(object state)
{
    // do not continue the code flow until the user has clicked on the grid. 
    // so when we debug, the code flow will literally stop here.
    var point = Utility.PickPoint(View);


    MessageBox.Show(point.ToString());
}

It may cause more trouble to consumers of your API except they used to manage their own threads. That's why async/await was invented.

Late answered 20/4, 2020 at 4:31 Comment(2)
Thank you Ken, is it possible that the addin starts from another thread and then its events do not block the main UI thread?Ape
@Ape Yes and no. Yes, you can call the blocking method in another thread and wrap it in another method. However, the wrapper method still needed to be called in another thread other than UI thread to avoid UI blocking. Because the wrapper will block the calling thread if it is synchronous. Although internally the wrapper blocks another thread, it still needs to wait for the result and blocks calling thread. If the caller calls the wrapper method in UI thread, UI is blocked.Late
M
0

I think the issue is on the design itself. If your API works on a particular element then it should be used in the event handler of this very element, not on another element.

For example, here we want to get the position of the click event on the Grid, the API needs to be used in the event handler associated with the event on the Grid element, not on the button element.

Now, if the requirement is to handle the click on the Grid only after we have clicked the Button, then the Button responsibility will be to add the event handler on the Grid, and the click event on the Grid will show the message box and remove this event handler added by the button so that it won't trigger anymore after this click ... (no need to block the UI Thread)

Just to say that if you block the UI thread on the button click, I don't think the UI thread will be able to trigger the click event on the Grid afterward.

Mcabee answered 20/4, 2020 at 9:10 Comment(0)
B
0

First of all, UI thread can not be block just like the answer you had got from your early question.
If you can agree with that, then avoiding async/await to make your customer do less modification is doable, and even don't need any multi-threading.

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
    {
        Utility.PickPoint(View, (x) => MessageBox.Show(x.ToString()));
    }
}

public static class Utility
{
    private static Action<Point> work;

    public static void PickPoint(Grid grid, Action<Point> work)
    {
        if (Utility.work == null)
        {
            grid.PreviewMouseLeftButtonUp += Grid_PreviewMouseLeftButtonUp;
            Utility.work = work;
        }
    }

    private static void Grid_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        var grid = (Grid)sender;
        work.Invoke(e.GetPosition(grid));
        grid.PreviewMouseLeftButtonUp -= Grid_PreviewMouseLeftButtonUp;
        Utility.work = null;
    }
}   

But if you want to block the UI thread or "code flow", the answer will be that it's impossible. Because if UI thread was blocked, there is no further input can be received.
Since you kind of mentioned about console app, I just do some simple explanation.
When you run a console app or call AllocConsole from a process that didn't attach to any console(window), conhost.exe which can provide a console(window) will be executed and console app or caller process will be attach to the console(window).
So any code you write that could block caller thread such as Console.ReadKey will not block console window's UI thread, any that's the reason why when console app waiting to your input but still can respond to other input like mouse clicking.

Brittabrittain answered 20/4, 2020 at 19:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.