This question already has answers here:
Is it possible to await an event instead of another async method?
(11 answers)
Closed 1 year ago.
I'm currently building an application where a user has a list of items that need to be triggered.
The operator triggers this using a shortcut ("ADD"-button).
Now most items only consist out of one action which is being executed, however, there are some items that include multiple steps. These steps need to be controlled by the operator.
So eventhough it is one item which is being triggered, the steps inside of the item need to be controlled by the shortcut.
private void function triggerItem()
{
// Perform Step 1
// PAUSE (AWAIT KEYPRESS)
// Perform Step 2
// PAUSE (AWAIT KEYPRESS)
// Perform Step 3
}
So for now in order to test it, I have just used a messagebox to "pause" in between the different steps, but I would like to make it a bit more clear. So I have created a panel with a keydown event that can capture the "ADD"-button being pressed to make the function continue.
But I think I would need a sort of Task that I can await the result for before continuing with the rest of the actions. Except I can't see how I can await a keydown event from a panel to return a value to the await function.
How could I achieve this?
private void showWarning()
{
// SHOW THE WARNING
panWarning.Show();
// ENSURE THE FOCUS IS SET TO THE PANEL TO CAPTURE THE KEY DOWN EVENT
panWarning.Focus();
// AWAIT THE KEYPRESS TO CONTINUE THE FUNCTION
}
private void panWarning_KeyDown(object sender, KeyEventArgs e)
{
// CHECK WHICH BUTTON WAS PRESSED
switch (e.KeyCode)
{
case Keys.Add:
// CONTINUE
break;
case Keys.Escape:
// ABORT
break;
}
}
Any suggestions?
Thank you.
You can use a TaskCompletionSource to await anything. To solve your problem, create one and set its result in an event handler.
A method like this one will do it:
public async Task WaitForKey(Control control)
{
var tcs = new TaskCompletionSource<bool>();
void LocalHandler(object s, EventArgs a)
{
tcs.SetResult(true);
}
control.KeyDown += LocalHandler;
await tcs.Task;
control.KeyDown -= LocalHandler;
}
And you'd call it like this:
private async Task TriggerItem()
{
// Perform Step 1
await WaitForKey(this);
// Perform Step 2
await WaitForKey(this);
// Perform Step 3
}
Note that if you are awaiting a keypress on the form itself, you may need to set KeyPreview to true.
Related
The application is a machine control, so it needs access to ui to show status etc. (I know, goes against the recommendation to separate UI and work code, but it is what it is, at least for now). The issue boils down to this: When one button event handler is not finished, another button needs to be clicked twice. First click gives the focus to the button, next click fires the event.
Here is the issue simplified to extreme. There are two buttons and a label. Stop button needs two clicks to stop the machine:
bool Stop = true;
private void Start_button_Click(object sender, EventArgs e)
{
RunMachine();
}
private void Stop_button_Click(object sender, EventArgs e)
{
Stop = true;
}
private void RunMachine()
{
Stop = false;
Status_label.Text = "Running";
do
{
Application.DoEvents();
Thread.Sleep(50);
}
while (!Stop);
Status_label.Text = "Stopped";
}
How can I make the button to react to the first click?
DoEvents() is bad. Don't use it.
If you have to use it (e.g. as workaround), then you are adding technical debt and likely to pay in the future, similar to your case.
A better approach is to run work inside the task and use cancellation token, but in your case the minimum modification required is this (add async modifier to a method):
while (!Stop)
{
await Task.Delay(50);
// or
await Task.Run(() => Thread.Sleep(50));
}
The UI should be responsive now.
The latter is simulating synchronous code, put it instead of Sleep, don't forget to invoke if there you have to modify UI.
Thank you! I wasn't aware of the implications of Doevents, and using async and await is just as simple. I added a counter to show myself that the toy example is doing what I think it is. To make the answer complete and to help other noobs like me that might search answers for the same issue, here is the full example again. This works as wanted (stops with one click) and doesn't leave the RunMachine() running if the main form is closed without clicking stop. (My real application has enough code in the form closing event to prevent that, but I certainly wasn't aware of the trap.)
bool Stop = true;
private async void Start_button_Click(object sender, EventArgs e)
{
await RunMachine();
}
private void Stop_button_Click(object sender, EventArgs e)
{
Stop = true;
}
internal async Task RunMachine()
{
Status_label.Text = "started";
Stop = false;
int i=0;
do
{
await Task.Delay(500);
Status_label.Text = i.ToString();
i++;
} while (!Stop);
Status_label.Text = "Stopped";
}
I am trying to have a callback function in xamarin forms to alert the user that the task is finished. I haven't figured it out yet and am not sure what is wrong.
Any help is appreciated with the proper way of doing this.
The code below is in a button click event. The method FinishPurchase never gets called.
Task<bool> task = StockDataController.Instance.WasItemPurchased(CloudInfo.IAPMonthlySubscriptionProductID);
Task continuation = task.ContinueWith(t=>FinishPurchase());
continuation.Wait();
Previously I was calling the code below in the button click event but I want to execute specific code after the method WasItemPurchased is executed,etc.
bool result=await StockDataController.Instance.WasItemPurchased(CloudInfo.IAPMonthlySubscriptionProductID)
The if statement, below, will be called once WasItemPurchased completes.
bool isResultSuccessful = await StockDataController.Instance.WasItemPurchased(CloudInfo.IAPMonthlySubscriptionProductID);
if (isResultSuccessful)
{
//Do Something
}
else
{
//Do Something else
}
This question already has answers here:
Execute task in background in WPF application
(3 answers)
Closed 5 years ago.
I have a MainWindow.xaml that gets populated with UserControls. I have a control in the MainWindow.xaml that has a button. The button is bound to the following command in MyViewModel.cs:
public ICommand MyCommand
{
get
{
return Get(() => MyCommand, () => new RelayCommand(ExecuteMyCommand, CanExecuteMyCommand));
}
}
public void ExecuteMyCommand()
{
Messenger.Default.Send(new NotificationMessage(this, MainWindowMessageBus.ShowPopup1), MainWindowMessageBus.Token);
Method1();
Messenger.Default.Send(new NotificationMessage(this, MainWindowMessageBus.ShowPopup2), MainWindowMessageBus.Token);
Method2();
}
public bool CanExecuteInsertBedCommand()
{
return true;
}
Method1() and Method2() execute code that take about 5 seconds each. The messengers are sending messages to the codebehind MainWindow.xaml.cs:
Popup1 popup1;
Popup2 popup2;
private void NotificationMessageReceived(NotificationMessage msg)
{
switch(msg.Notification)
{
case MainWindowMessageBus.ShowPopup1:
popup1 = new Popup1();
ControlContainer.Children.Add(popup1);
break;
case MainWindowMessageBus.ShowPopup2:
// Remove popup1
ControlContainer.Children.Remove(popup1);
popup1 = null;
// Add popup2
popup2 = new Popup2();
ControlContainer.Children.Add(popup2);
break;
default:
break;
}
}
Expected results: upon pressing my button, Popup1 shows immediately and stays in the window while Method1() executes. Then, Popup2 replaces Popup1 in the window and Method2() executes.
Actual results: upon pressing my button, nothing happens in the UI. Method1() and Method2() execute as expected, and after they both run (~10 seconds), Popup2 is displayed. I never see Popup1.
I tried a couple threading/async methods, but they didn't work. How can I get ExecuteMyCommand() to update the UI as it goes?
EDIT
Ok so I can't answer my own question for some reason. I don't think this question is a duplicate of the specified thread, since that would imply that I already knew I needed to run Method1() in the background, which I did not. Anyways, here's what I changed:
public async void ExecuteMyCommand()
{
Messenger.Default.Send(new NotificationMessage(this, MainWindowMessageBus.ShowPopup1), MainWindowMessageBus.Token);
await Task.Run(() =>
{
Method1();
});
Messenger.Default.Send(new NotificationMessage(this, MainWindowMessageBus.ShowPopup2), MainWindowMessageBus.Token);
Method2();
}
Don't call a method who take several time in the UI thread.
You can try by created a custom window.
In this you can show all custom information for your user.
As your method take several time, you can add a cancel button for exemple.
You can also add a progress bar. When a method take time, it's great to know the state of the system.
Try this, when you press your button -> call a command to lunch a thread
In your command :
void Command()`
{
// run a thread, create a window and show it
// you can declare the thread outside if you want use something like join
Thread threadExecute = new Thread(Execute);
w = new WinTest();
w.Topmost = true;
w.Show();
threadExecute.Start();
}
// Call when your thread terminated
void finish()
{
w.Close();
}
void Execute()
{
// Your big method 1
// Change the window information
// big method 2
this.Dispatcher.Invoke( finish );
}
I have a method that is called after the initialization component, the method signature is like:
public async void method_name ()
{
// code ...
}
Inside that method, I have a loop running with 4 different if statements. I need the loop to pause at each if statements and wait for the user to press a button. Since pressing that button will add info and stuff. Once the button is pressed, I want the loop to continue and of course pause at the next if statements.
I thought about doing it like await Task.Delay(30000); but if the user is done entering the info before that timer is over, he/she will just be waiting. and That's not efficient.
You can do that with TaskCompletionSource. Create it and await its Task property and when a button is clicked use it to complete that task. That allows to asynchronously wait for the user input without blocking a thread or using Task.Delay.
TaskCompletionSource<bool> _tcs;
async Task Foo()
{
// stuff
_tcs = new TaskCompletionSource<bool>();
await _tcs.Task
// other stuff
}
void ButtonClicked(object sender, EventArgs e)
{
_tcs.SetResult(false);
}
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.
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";
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
}
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.
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.
Try it : e.Handled = true; It works to prevent KeyEventArgs, for example.