Let's assume we have this:
private void Input_KeyDown(object sender, KeyEventArgs e)
{
e.Handled = true;
}
And then i add async-await like this:
private async void Input_KeyDown(object sender, KeyEventArgs e)
{
await Task.Delay(1000);
e.Handled = true;
}
Shouldn't that do the same, but only delay it for a second? So why doesn't it?
NOTE:
I'm not seeking for debugging help, i'm just wondering why can't i use async-await to delay handling the KeyDown
i'm just wondering why can't i use async-await to delay handling the KeyDown
Event handlers are synchronous by definition. So, unless your code has a way to inform the event handler that it is acting asynchronously (e.g., a WinStore-style Deferral), then it must do all "communication" (e.g., KeyEventArgs.Handled) before its first await.
If you need to delay input in general, I recommend converting the code to use Reactive Extensions.
The problem is that you're trying to use await in an event handler. The "synchronous part" of event handler does nothing.
void OnKeyDown()
{
Keys keyData = ...;
KeyEventArgs args = new KeyEventArgs(keyData);
// Thread-safe event raiser,
// equalient to KeyDown(this, args);
RaiseKeyDownEvent(args);
// Returns immediatelly when you reach
// the first await method call
bool isHandled = args.Handled;
// In the meantime event handler
// is awaiting for Task.Delay(),
// but we have already saved Handled value
}
// This code does nothing useful
private async void Input_KeyDown(object sender, KeyEventArgs e)
{
// Will return there
await Task.Delay(1000);
// The execution will be continued from there
e.Handled = true;
}
// But this code will handle input, but without any delay
private async void Input_KeyDown(object sender, KeyEventArgs e)
{
e.Handled = true;
await Task.Delay(1000);
}
Related
I'm doing an autoclicker and I have a problem: I want the program to execute the Click() method while I hold the C key.
I tried this. once I press a key and my code stuck's an infinite loop. I saw a similar problem on StackOverflow but it didn't work for me.
Here is my code:
private static bool IsHolded = false;
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
Keys key = e.KeyData;
if (key == Keys.C)
{
IsHolded = true;
while (IsHolded)
{
Click();
}
}
}
private void Form1_KeyUp(object sender, KeyEventArgs e)
{
IsHolded = false;
}
Based on your concept you can implement it in different ways. You can run the inner code in another thread and handle the KeyUp events via CancellationToken. Here is an implementation:
CancellationTokenSource cts = new CancellationTokenSource();
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
Keys key = e.KeyData;
if (key == Keys.C)
{
IsHolded = true;
ThreadPool.QueueUserWorkItem(new WaitCallback(DoSomeWork), cts.Token);
}
}
private void DoSomeWork(object obj)
{
CancellationToken token = (CancellationToken)obj;
while (!token.IsCancellationRequested)
{
//Do Some Work
}
}
private void Form1_KeyUp(object sender, KeyEventArgs e)
{
cts.Cancel();
IsHolded = false;
}
There is no need to initialize IsHolded variable unless you want to use it to prevent multiple executions when you hold the 'c' key. For this you should check the IsHolded value within the IF statements in the KeyPress method:
if (key == Keys.C && !IsHolded)
Remember, if you cancel the CancellationTokenSource, it remains in the cancel state and doesn't work next time when you press the 'c' key. So, if you want to handle it you should Dispose and reinitialize the old 'ctx' in the KeyUp method. Here are two articles about CancelationToken.
Cancellation in Managed Threads
and
Register callbacks for cancellation requests
well i am having two buttons on a form and I want to start data transfer with the first button and stop on the press of a second button.
code is:
private void stdaq_Click(object sender, EventArgs e)
{
stopped = false;
//while (stopped == false)
if (sender == spdaq)
{
stopped = true;
///break;
Process();
}
else if (sender == stdaq)
{
Process();
}
}
here stdaq is the start button and spdaq is the stop button, the process function is a function which i am implementing and in that with the stopped variable of bool type i am implementing two different functions inside process method, but i want to continually check whether the stop button is pressed or not but here with this code i got no success.
so please help me with how to pass the value true to the stopped variable inside the event click function of start button itself on the press of stop button.
Create cancellation token, start asynchronous Task in button start event handler put your method in this Task, pass reference to this cancellation token and use
it to stop this task in Stop button event handler when you'll need it later.
More information : https://msdn.microsoft.com/en-us/library/jj155759.aspx
Example of how you can use it:
static CancellationTokenSource cts;
static Task t;
private void Method()
{
while (!cts.IsCancellationRequested)
{
// your logic here
}
t = null;
}
private void stdaq_click (object sender, EventArgs e)
{
if(t != null) return;
cts = new CancellationTokenSource();
t = new Task(Method, cts.Token, TaskCreationOptions.None);
t.Start();
}
private void spdaq_Click(object sender, EventArgs e)
{
if(t != null) cts.Cancel();
}
Use two separate Handlers for the start and the stop button. This makes your logic much simpler to follow. Then do soemthing like this:
private void stdaq_Click(object sender, EventArgs e) // Start
{
Process(true);
}
private void spdaq_Click(object sender, EventArgs e) // Stop
{
Process(false);
}
Or even better: Create two seperate Methods StartProcess() and StopProcess().
I have event
button.Click += ProcessClick;
and my event handler ProcessClick is:
private void async ProcessClick(object o, EventArgs e){
await LongOperation()
}
When I will click button quickly many times,
How to process only the first thread and skip other threads, until the first is done?
Disabling button after first click is not a solution...
EDIT: utilizing any type of thread constructions is acceptable: Tasks, Semaphores, locks and so on.
You can remove the event after the first click and add it again at the end of processing the first click.
private async void button1_Click(object sender, EventArgs e)
{
Debug.Write("Running click event");
this.button1.Click -= button1_Click;
await Task.Run(() => Task.Delay(5000));
this.button1.Click += button1_Click;
}
private bool longOperationRunning;
private void async ProcessClick(object o, EventArgs e){
if (longOperationRunning)
return;
longOperationRunning = true;
await LongOperation()
longOperationRunning = false;
}
I have a question regarding looping with button click event, I've tried many methods & searched many pages in search for a simple answer for the past hour, but the truth is each answer just looks like alien code, probably because I'm still very new to developing.
Here's a simplified version of what I'm trying to do :
private string Message = "Hello";
private void Spam(bool loop)
{
if (loop == true)
{
while (loop == true)
{
MessageBox.Show(Message);
}
}
else { MessageBox.Show("Spamming has stopped !! "); }
}
private void button1_Click(object sender, EventArgs e)
{
Spam(true);
}
private void button2_Click(object sender, EventArgs e)
{
Spam(false);
}
Obviously this isn't my API, or it'd be a useless thing to invent, however, the code itself is long & you guys always ask for "relevant code" (No disrespect), so there it is.
My problem : Breaking out of the spam loop upon clicking button 2, the code to me looks decent enough for the API to figure out, but each time button 1 is clicked, the API freezes.
Use a background worker to do your work. You can use the cancellation feature to break out of it when you're done. Your loop as you have it will block the UI thread when executed syncronously, which is why your GUI becomes unresponsive. Note if you do any interaction with the UI in the do work delegate, you need to marshal back onto the UI thread (via invoke for example).
private BackgroundWorker _worker = null;
private void goButton_Click(object sender, EventArgs e)
{
_worker = new BackgroundWorker();
_worker.WorkerSupportsCancellation = true;
_worker.DoWork += new DoWorkEventHandler((state, args) =>
{
do
{
if (_worker.CancellationPending)
break;
Console.WriteLine("Hello, world");
} while (true);
});
_worker.RunWorkerAsync();
goButton.Enabled = false;
stopButton.Enabled = true;
}
private void stopButton_Click(object sender, EventArgs e)
{
stopButton.Enabled = false;
goButton.Enabled = true;
_worker.CancelAsync();
}
Update 2019:
BackgroundWorker is now largely obsolete, replaced by the async/await feature in later versions of C# which is easier to use. Here is an example of how to achieve the same thing using that feature:
private CancellationTokenSource _canceller;
private async void goButton_Click(object sender, EventArgs e)
{
goButton.Enabled = false;
stopButton.Enabled = true;
_canceller = new CancellationTokenSource();
await Task.Run(() =>
{
do
{
Console.WriteLine("Hello, world");
if (_canceller.Token.IsCancellationRequested)
break;
} while (true);
});
_canceller.Dispose();
goButton.Enabled = true;
stopButton.Enabled = false;
}
private void stopButton_Click(object sender, EventArgs e)
{
_canceller.Cancel();
}
There's one important thing to remember:
While your code is being executed, the user cannot interact with your user interface.
That means: You first need to exit the loop (i.e. return from the Spam method), and then the user can click Button2.
That's a hard truth, because it means you cannot write the code in the way you wanted to. Fortunately, there are a few ways to work around that:
Don't use a loop. Use some kind of timer to do the "spamming". Button1 starts the timer, Button2 stops it. What kind of timer is available depends on the user interface library you use (WinForms has a Timer, WPF has a DispatcherTimer).
Do the "spamming" in a background thread. This will allow your user interface to stay responsive, and you can communicate with the background thread, for example, by setting a volatile Boolean. This, however, is an advanced topic (and can quickly lead to complex synchronization issues), so I suggest that you try the other option first.
When you click button1 the Spam method is called and loop is starting. When you click button2 Spam method is called but it's not the same. It's the second execution, so it will check the condition and won't enter into the loop, but the loop in the first call sill will be running.
You should use a flag and the loop should use that flag to determine whether it should be still running. It should look something like that:
bool run = false;
string message = "This API is not original";
private void Spam()
{
while (run == true)
{
MessageBox.Show(message);
}
}
}
private void button1_Click(object sender, EventArgs e)
{
message = "Hellooo";
flag = true;
Spam();
}
private void button2_Click(object sender, EventArgs e)
{
flag = false;
}
Take a look at this concept:
private bool loop = false;
private void Start()
{
loop = true;
Spam("Some Message??");
}
private void Spam(string message)
{
while (loop)
{
MessageBox.Show("This API is not original");
}
}
private void button1_Click(object sender, EventArgs e)
{
loop = true;
}
private void button2_Click(object sender, EventArgs e)
{
loop = false;
}
However, the user won't be able to press a button if a MessageBox keeps popping up as it takes up the main UI thread. In order to prevent this you could use BackgroundWorker or start a new thread.
I've a backgroundworker which take care of a timer in my application. This is the code:
protected override void OnLoad(EventArgs e)
{
// Inizializzo il backgroundworker
bgwTimer.WorkerReportsProgress = true;
bgwTimer.WorkerSupportsCancellation = true;
bgwTimer.DoWork += (bgwTimer_DoWork);
bgwTimer.RunWorkerCompleted +=(bgwTimer_RunWorkerCompleted);
bgwTimer.ProgressChanged += (bgwTimer_ProgressChanged);
}
void bgwTimer_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
throw new NotImplementedException();
}
void bgwTimer_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
throw new NotImplementedException();
}
Basically the event "ProgressChanged" is never fired and so I cannot update the status of a progressbar.
The event DoWork is linked to this method:
void bgwTimer_DoWork(object sender, DoWorkEventArgs e)
{
int i = 0;
if (bgwTimer.CancellationPending)
{
e.Cancel = true;
}
else
{
while (bgwTimer.IsBusy)
{
Thread.Sleep(1000);
bgwTimer.ReportProgress(i);
refreshTimer();
}
}
}
By my side the code looks good and it runs fine. As you can see the ReportProgress method is called but the event is not fired. Any hints?
UPDATE:
Whops! I found that the event "bgwTimer_ProgressChanged" is fired only if I run the RunWorkerAsync right after the declaration of event. Basically:
bgwTimer.ProgressChanged += (bgwTimer_ProgressChanged);
bgwTimer.RunWorkerAsync(); //this works!
Since I run the worker when the user press a button, the event is not triggered.
Here's the code of click event button:
private void btnNext_Click(object sender, EventArgs e)
{
this.TopMost = true;
btnNext.Enabled = false;
progressBar1.Step = 0;
if (_bgwTimer.IsBusy)
_bgwTimer.CancelAsync();
else
_bgwTimer.RunWorkerAsync();
}
Put a breakpoint, or a Debug.Print or System.Windows.Forms.Messagebox just before bgwTimer.ReportProgress(i), to verify that you're actually entering the while loop.
Note that the BackgroundWorker is not actually a timer; it's a wrapper for a thread that provides a threadsafe invoking layer for your user interface.
Your if (bgwTimer.CancellationPending) { } should be inside the while loop, not outside it. It will only get checked once in your current code.
Note that, if you're inside the DoWork event handler, then by definition you're running an asynchronous process, so IsBusy should always be true (according to the MSDN documentation), and therefore your while is an infinite loop. But check it with your debugger.
It's not raising the event because the value of i is always zero, which is helpfully undocumented but I found out the same thing when building a background worker a while back.
You forgot to start the worker. Add this line to your OnLoad() method:
bgwTimer.RunWorkerAsync();
In the DoWork-Method replace bgwTimer through ((BackgroundWorker)sender). Maybe this is the problem