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);
}
Related
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.
Is it possible to stop an ongoing process with a button click in Windows form application?
For example, let's say there are 2 buttons, "START" and "STOP"
When you press "START", it will start an infinite loop, printing numbers from 1 to infinity.
When I press "STOP", the process should stop at that moment.
But the problem is, I cannot press the "STOP" button as it does not allow me, since there's an ongoing process.
Is there a way to overcome this?
I know there's something called "MethodInvoker", but I have no idea how that works or whether it is relevant to this.
private bool keepRunning = true;
public Form1()
{
InitializeComponent();
}
private void StartBtn_Click(object sender, EventArgs e)
{
var number = 1;
while (keepRunning)
{
Thread.Sleep(1000);
MesgeLabel.Text = "" + number++;
}
}
private void StopBtn_Click(object sender, EventArgs e)
{
//Cannot even click this button
keepRunning = false;
//or
Application.Exit();
}
EDIT 1:
If you need to interact with UI controls, doing it from a background task would throw invalid operation -> illegal cross thread exception. To overcome this,
check Control.InvokeRequired
if(myLabel.InvokeRequired)
myLabel.Invoke(new Action(() => myLabel.Text = newText));
else
myLabel.Text = newText;
You can start a Task by providing a CancellationToken and cancel the operation when the stop button is clicked.
The task will execute the infinite loop on another thread and your main thread (the UI thread) should not be affected and should be accessible.
Try this:
/*
Please add these on top of your form class
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
*/
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
CancellationTokenSource cancellationTokenSource;
CancellationToken cancellationToken;
private void CountToInfinity()
{
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
Debug.WriteLine(new Random().Next());
}
}
private async void button1_Click(object sender, EventArgs e)
{
if (cancellationTokenSource == null)
{
cancellationTokenSource = new CancellationTokenSource();
cancellationToken = cancellationTokenSource.Token;
Task.Run((Action)CountToInfinity, cancellationToken);
}
}
private void button2_Click(object sender, EventArgs e)
{
if (cancellationTokenSource != null)
{
cancellationTokenSource.Cancel();
cancellationTokenSource.Dispose();
cancellationTokenSource = null;
}
}
}
If you have spawned a new process then you can call kill method.
Process myProcess = Process.Start("Notepad.exe")//starts new process
myProcess.Kill();// kills the process. save reference to myProcess and call kill on STOP button click
If you have started new thread then call abort method to stop the thread.
Thread thread = new Thread(new ThreadStart(method));
thread.Start();
thread.Abort(); // terminates the thread. call abort on STOP button click
When you press the "start" button, the code that runs and prints the numbers will run on the ui thread. (from your explanation, i assume that all you have is the message handler for the button press event and nothing else. e.g.: Not setting up a seperate thread.).
Running an infinite loop on the ui thread means, that you do not get any more time for processing other messages. (the thread that is responsible for processing the ui messages is stuck in your infinite loop.)
So, in order to be able to press the "stop" button, you need to run the code with the infinite loop in a different thread or in a different process altogether. This is what Arjun is trying to tell you. (if you want the code in the infinite loop to access resources from your form app, you need a thread. [the thread is inside the forms app process.])
please note: if you create a thread and run your number printing code inside that thread, this will not be the ui thread. Thus, you will not be able to interact with the forms controls as if you'd be on the ui thread. (i.e.: trying to set the windows.text in order to display your numbers will most likely throw an exception.)
On button click I have fired call to StartContinuousThread which keeps polling the server every one second.
public class ThreadsWindow
{
CancellationTokenSource wtoken = new CancellationTokenSource();
private void btnStartTest_Click(object sender, RoutedEventArgs e)
{
StartContinuousThread();
}
void StartContinuousThread()
{
while(true)
{
var fact = Task.Factory.StartNew(() =>
{
CallServer();
Task.Delay(1000, wtoken.Token);
},
wtoken.Token);
}
}
}
StartContinuousThread starts executing, but btnStartTest_Click event handler finishes its execution.
How StartContinuousThread method would be able to update UI in this
case?
I wonder whether StartContinuousThread is also terminated with event handler, since there is no wait keyword for re-joining.
Please help!
If you goal is to poll the server every second you have a number of problem.
There is no loop. You execute the method once and then stop.
You create a task using Delay and then ignore it. You ought to be creating a continuation of that task to do the rest of the work to actually not do anything for a second.
Here is an implementation that addresses those issues:
private async Task StartContinuousThread(CancellationToken token)
{
while (true)
{
token.ThrowIfCancellationRequested();
await Task.Run(() => CallServer());
await Task.Delay(1000, token);
}
}
Another possibility, especially if you're using an older version of C#, would be to use a timer to run some code every second.
As for updating the UI; you can do so freely anywhere outside of the call to Task.Run in this example. In your example you'd need to use some mechanism to marshal back to the UI thread, such as capturing the UI's synchronization context and posting to it.
I am developing metro application in c#, I am using async and await keywords to create make metro async operatons (downloading data etc.). I always show modal "Please wait" dialog. I would like to add "Cancel" button to this modal dialog to allow cancel the background operation.
But I am not sure how to cancel processing task... Is there any example how to do that?
This is the example of my task:
// Starts task
public void StartTask()
{
// show the modal dialog with progress
_progressDialog.IsOpen = true;
_progressDialog.CancelClick += ProgressDialog_CancelClick;
await ToWork();
_progressDialog.IsOpen = false;
}
// Task which takes some seconds
private async Task DoWork()
{
await DownloadData();
await ProcessData();
}
// Cancel task
private void CancelClick(object sender, RoutedEventArgs e)
{
// hide the modal dialog with progress
_progressDialog.IsOpen = false;
// TODO: Cancel task
GoBack(this, e);
}
You can decide to implement DownloadData and ProcessData such that they take a CancellationToken and pass that to them when you need to cancel.
public Task DownloadData(CancellationToken tok)
{
tok.ThrowIfCancellationRequested();//check that it hasn't been cancelled.
//while doing your task check
if (tok.IsCancellationRequested)
{
// whatever you need to clean up.
tok.ThrowIfCancellationRequested();
}
}
For usage you can create a CancellationTokenSource and pass the token to the method.
var source = new CancellationTokenSource();
await DownloadData(source.Token);
When you need to cancel you can call Cancel() on the source
source.Cancel();
I have a case where, I need to print something in textbox1 then wait for a second, make an image visible and then again wait for a second then print something in textbox2 in one button click. When I wrote a sleep after the printing in textbox1 in the Click event of the button. I see that the printing on the UI is done all at a time, i.e. I expect it to be done sequentially one after another with a pause, but since its a single event handle it waits till end and show up on UI all at a time in the end.
I don't know if it work like for Windows phone 8 but you can "sleep" making the method async and calling
await Task.Delay(1000);
public async void myMethod(){
printString1();
await Task.Delay(1000);
showImage();
await Task.Delay(1000);
printString2();
}
You can put your code in a thread, and use the dispatcher when you need to update the UI:
private void Process()
{
this.Dispatcher.BeginInvoke(() => this.TextBox1.Text = "Hello");
Thread.Sleep(1000);
this.Dispatcher.BeginInvoke(() => this.Picture1.Visibility = Visibility.Visible);
Thread.Sleep(1000);
this.Dispatcher.BeginInvoke(() => this.TextBox2.Text = "World");
}
Then, to start the thread (when the user clicks on the button):
var thread = new Thread(this.Process);
thread.Start();