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";
}
Related
Im trying to make a simple application to learn some things in c# (Visual Studio).
For now i am building a music player, and one of the actions is fading out the music at the button click event.
I've got no problem with building a fade-out part, i made a while loop and put the volume down with 1% eacht time the loop is running. Also i update a label with the fade value.
Only problem is, for slowing down the fading i'm using the Thread.Sleep event, and that part is freezing my application, and also is blocking any updates to my text label with the fade value.
The fading is working fine, so the only part I have to work on is another option to build some delay in. On some topics over here i did read about the timer, and i added a timer component in Visual Studio. Only problem, I am new to c# and don't know how to use it correctly in this while loop.
Can anybody give me some help?
The current code is:
private void BtnPodiumtune1Fadeout_Click(object sender, EventArgs e)
{
PlayerPodiumtune1.settings.volume = 100;
fade1 = 100;
while (fade1 != -1)
{
PlayerPodiumtune1.settings.volume = fade1;
Fadelevel1.Text = fade1.ToString();
System.Threading.Thread.Sleep(30);
fade1 = fade1 - 1;
}
PlayerPodiumtune1.Ctlcontrols.stop();
}
You could use a pattern like this instead of a timer. A timer is a fine way to go, just throwing this option out there:
private async void button_Click(object sender, EventArgs e)
{
if (Monitor.TryEnter(sender))
{
int fade1 = 1000;
while (fade1 != -1)
{
await Task.Delay(30);
fade1--;
}
}
}
So sender is the button, and Monitor.TryEnter prevents the function from being run again until the function is done. async tells the framework that this function can be executed asynchronously and is necessary for await. await returns control of the thread to the UI until the task is done.
PS--You're going to need something like Monitor.TryEnter to prevent re-entrancy in a timer-based solution as well, by the way.
This is a Console Application in C#:
using System;
namespace WaitAsync
{
class Program
{
static void Main(string[] args)
{
bool ok = false;
Console.Write("EnterTime (Seconds): ");
int time = Convert.ToInt32(Console.ReadLine()) * 1000;
while (ok != true)
{
System.Threading.Thread.Sleep(time);
ok = true;
Console.WriteLine("Waiting Time Just Finished");
}
Console.ReadLine();
}
}
}
I wonder why click event does not show me the hp value of every loop. Its only shows me hp at the start and 0 at the end
private void button_Click(object sender, RoutedEventArgs e)
{
while (player.Hp > 0)
{
int vypocet = player.Damage(player2);
player.Hp = vypocet;
label.Content = vypocet;
}
}
This should be everything you need to know
So as i said its only show me start hp and hp after whole fight and i dont know why its not show me other numbers if i am using while loop
The reason is that the event handler runs on the UI thread. This means, the changed value can be reflected in the user interface only after the whole loop ends.
If you wanted to show the progress, you would have to run the computation on another thread and use the Dispatcher to notify the UI thread about the changes.
An alternative is to yield the UI thread regularly to give the UI a chance to update. This is however not very clean.
await Dispatcher.Yield(DispatcherPriority.ApplicationIdle);
Because UI controls will be updated after button_Click method exits.
Try change method to asynchronous and use Task.Delay which will "release" UI thread for updating controls
private async void button_Click(object sender, RoutedEventArgs e)
{
while (player.Hp > 0)
{
int vypocet = player.Damage(player2);
player.Hp = vypocet;
label.Content = vypocet;
await Task.Delay(100);
}
}
I have a task that runs in the form_load event of a usercontrol in winforms:
private void ucDeviceInsert_Load(object sender, EventArgs e)
{
System.Threading.Tasks.Task getTBox = System.Threading.Tasks.Task.Run(async () =>
{
await AVeryLongRunningProccess();
});
pbImage.Image = Properties.Resources.Remove;
getTBox.Wait();
pbImage.Image = Properties.Resources.Insert;
btnNext.Visible = true;
tmrDeviceInsert.Enabled = true;
tmrDeviceInsert.Start();
}
private void tmrDeviceInsert_Tick(object sender, EventArgs e)
{
Next();
}
I change the image of the picture box to inform the user the progress of the long running process. That part works fine, however the button doesn't show, and the timer never starts. I've stepped through the code, and I can confirm that it is running without any problems, which makes this even more baffling. Any ideas what would be causing this issue?
Task.Run is for pushing CPU-intensive work off the UI thread. Since you're calling an asynchronous method, I suspect it's not CPU-intensive.
So, you can just use async and await:
private async void ucDeviceInsert_Load(object sender, EventArgs e)
{
pbImage.Image = Properties.Resources.Remove;
await AVeryLongRunningProccess();
pbImage.Image = Properties.Resources.Insert;
btnNext.Visible = true;
tmrDeviceInsert.Enabled = true;
tmrDeviceInsert.Start();
}
Note that at the await, the UI is shown and the user can interact with it (that's the point).
getTBox.Wait() is going to try to complete that task synchronously. Therefore, the rest of the code after it won't happen until after the task completes.
I'd think you don't want your task to run synchronously at all, but rather handle its completion asynchronously, something like this:
getTBox.ContinueWith(() => updateStatusInUI());
I get data from database on a click.
I have an event handler which when triggered should show "data retrieving..." in status bar and should change to "Ready" again just before the event handler ends.
But the text updates only once, the second Ready one. How is it generally done?
private void Next_Click(object sender, RoutedEventArgs e){
this.footerText = "Waiting for dataRetreival";
someRandomTimeTakingMethod(); //Gets Data from DB.
this.footerText = "Ready";
}
Even though code executes line 2, the view updates only when the function is over, ie only the second one actually works.
You should put your data-intensive work on a background thread so the UI can update properly. This provides the best user experience.
To elaborate on FZysset's answer with some code...
private async void Next_Click(object sender, RoutedEventArgs e)
{
footerText.Text = "Waiting for dataRetreival";
IsEnabled = false;
await SomeRandomTimeTakingMethodAsync();
IsEnabled = true;
footerText.Text = "Ready";
}
private async Task SomeRandomTimeTakingMethodAsync()
{
await Task.Delay(TimeSpan.FromSeconds(new Random().Next(2, 5)));
// or await Task.Run(() => { ... });
}
The above example allows you to leverage await/async that was introduced in .NET 4.5. Notice how nicely it flows? No nonsense!
We're putting stuff onto the background thread so the UI can remain unblocked (thus it will show your updates to your status bar and allow user interaction.) Of course, you have to be careful not to update anything on the UI from your background thread.
If you are using an older version of .NET, you can just use TPL without async/await:
private void Next_Click(object sender, RoutedEventArgs e)
{
footerText.Text = "Waiting for dataRetreival";
IsEnabled = false;
Task.Factory.StartNew(() =>
{
SomeRandomTimeTakingMethod();
}).ContinueWith(t =>
{
IsEnabled = true;
footerText.Text = "Ready";
}, TaskScheduler.FromCurrentSynchronizationContext());
}
private void SomeRandomTimeTakingMethod()
{
Thread.Sleep(TimeSpan.FromSeconds(new Random().Next(2, 5)));
}
Two important things to note about the latter example:
You must provide TaskScheduler.FromCurrentSynchronizationContext() to the ContinueWith call, or you will encounter exceptions because the continuation is not on the UI thread. You must get the context in a method that isn't running on a background thread.
You will want to check for exceptions on the Task object in your ContinueWith.
This example is very rudimentary though. If you were to have a bunch of background operations kicked off with click handlers, you'd want to give yourself some helper classes/services to make life easier. (And investigate MVVM, which I cannot tell if you are using.)
A colleague of mine gave a presentation on using various asynchronous patterns in C# and .NET. You can check it out here: https://github.com/mtusk/TplLunchAndLearn
That's because you're "someRandomTimeTakingMethod" is launched on the UI Thread. Therefore it will not update the view until it is finished.
To go around this you have the following possibilities :
Make your method "someRandom..." asynchronous with a task, and use the await operator : http://msdn.microsoft.com/en-us/library/hh191443.aspx
Launch your randomTimeTaking method into a thread, and launch an event when your execution is finished, to update the footer text
I strongly recommend you the first option, for some sample : http://msdn.microsoft.com/en-us/library/hh873191.aspx
You need to run those lines asynchronously. You can do that using the Task class:
private void Next_Click(object sender, RoutedEventArgs e){
Task.Factory.StartNew(() => footerText = "Waiting for dataRetreival");
someRandomTimeTakingMethod(); //Gets Data from DB.
Task.Factory.StartNew(() => footerText = "Ready");
}
There is one way to do it using Dispatcher. The original post is here.
The code is:-
private void Next_Click(object sender, RoutedEventArgs e){
UpdateUI("Please wait for data retrieval", delegate() { someRandomTimeTakingMethod(); });
this.footerText = "Ready";
}
public delegate void NoArgsDelegate();
public void UpdateUI(string description, NoArgsDelegate operation)
{
this.FooterText= description;
DispatcherFrame frame = new DispatcherFrame();
DispatcherOperation dispatcherOperation = Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.ContextIdle, operation);
dispatcherOperation.Completed += delegate(object sender, EventArgs e)
{
frame.Continue = false;
};
Dispatcher.PushFrame(frame);
}
If my understanding is right, this uses Asynchronous programming, not different thread. The thread will update UI first and then call the someRandomTimeTakingMethod().
I have a button click event handler with a switch case in it that controls multiple buttons in one event handler.
I need to use a queue because while one button is clicked and doing some processing, second button click won't interfere with the first button click, but added to the queue. I don't want to use .enabled=false; because it'll discard the second click completely, and I'm currently editing someone's software at work so I don't want to break things that I don't know, so what are you suggesting?
The best idea, I think, is to create a producer/consumer queue.
Another question is explaining this technique.
Basically, the idea is to have a worker thread that will consume a queue to get the job to do, while other thread produce job by queuing operation in the queue.
I did succeed this with System.Collections.Queue
The code is :
private Queue<Button> Button_Queue = new Queue<Button>();
private bool isProcessing = false;
private void Button_Click((object sender, EventArgs e){
if(isProcessing){
Button_Queue.Enqueue(this);
}
else
{
isProcessing = true;
// code here
isProcessing = false;
while(Button_Queue.Count > 0){
Button_Queue.Dequeue().PerformClick();
}
}
of course mine is slightly different from this because I need to pass some variables and my click method is modified for this.
Dirty, but simple solution.
public partial class DataRefresh : Form //DataRefresh is just "some form"
{
...
...
public DateTime ClickTime; //Time when click is processed by system
public DateTime LastExecutionRunTime = DateTime.MinValue; //Time when the all the click code finish
private void buttonDataRefresh_Click(object sender, EventArgs e)
{
ClickTime = DateTime.Now;
if (ClickTime.Subtract(LastExecutionRunTime).TotalSeconds < 5 )
{
//It will keep returning - hopefully until all events in que are satisfied
return;
}
//Long running code
//Importing whole table from remote DB
...
...
//End of the Long running code
LastExecutionRunTime = DateTime.Now;
}
}