I'm playing with asynchronous operations and my goal right now is very simple. I just want to update a text field at the beginning of a large computation, and the problem I'm facing is that the text field gets updated but only when calculate() returns, even though the method computingMessage() is called immediately:
private void Window_PreviewKeyDown(object sender, KeyEventArgs e)
{
e.Handled = true;
annunciate();
calculate();
}
private void annunciate(){
Thread updateUI = new Thread( new ThreadStart( computingMessage ));
updateUI.Start();
}
private void computingMessage(){
txtVerification.Dispatcher.Invoke((Action)(
() => txtVerification.Text = "Calculating..."
));
}
Please, check Task object.
private void Button_Click(object sender, RoutedEventArgs e)
{
TextBoxOutput.Text = "calculating...";
Task.Factory
.StartNew(() => Calculate())
.ContinueWith(t =>
{
TextBoxOutput.Text = t.Result.ToString(CultureInfo.InvariantCulture);
}, TaskScheduler.FromCurrentSynchronizationContext());
}
private int Calculate()
{
Thread.Sleep(2000); //--similate working....
return Environment.TickCount ^ 43;
}
I hope it helps you.
Although I agree with #dbvega in spirit. I cannot agree with the usage of Task.Factory.StartNew. Nor should you use Dispatcher.Invoke. By default, Task.Factory.CurrentScheduler will be set to the WPF message pump scheduler when running WPF. When running WinForms, there is a WinForm scheduler that is automatically set...
private async void Button_Click(object sender, RoutedEventArgs e)
{
TextBoxOutput.Text = "calculating...";
var result = await Task.Run(Calculate);
TextBoxOutput.Text = result.ToString(CultureInfo.InvariantCulture);
}
private int Calculate()
{
Thread.Sleep(2000); //--similate working....
return Environment.TickCount ^ 43;
}
Related
C# Calling a function repeatedly to increase value of label automatically
This is a WPF program. It has a label on it (minuteTimerLabel) that displays a two digit number, starting from 00, 01, 02 and so on up to 99. I want two features from this label. First, when the left mouse button goes up on the label (simulating a click), the number increases by 1. I have been able to implement this. But I have problem with the second feature.
private void MinuteTimerLabel_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
while(true)
{
if (timerMinute == 99)
{
return;
}
timerMinute += 1;
minuteTimerLabel.Content = String.Format("{0:00}", timerMinute);
Thread.Sleep(250);
}
}
When the left mouse button is held down on the label for a few seconds, the number should keep increasing one by one automatically.
timerMinute is a global int variable.
With my current code, the entire program locks up and nothing works. When I remove the while(true), number increases only once when pressing mouse down. If I release the mouse and press again, it works but again only once.
To avoid locking up the UI you need to use some form of async coding. I'd suggest using Microsoft's Reactive Framework.
NuGet "System.Reactive.Windows.Threading" to get the bits and add using System.Reactive.Linq; to the top of your code.
Then you can do this:
private IDisposable _subscription = null;
private void MinuteTimerLabel_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_subscription =
Observable
.Interval(TimeSpan.FromMilliseconds(250.0))
.Select(x => String.Format("{0:00}", x))
.ObserveOnDispatcher()
.Subscribe(x => MinuteTimerLabel.Content = x);
}
private void MinuteTimerLabel_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
_subscription.Dispose();
}
It's super simple and super clean.
private IDisposable _subscription = null;
private int _counter = 0;
private void MinuteTimerLabel_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_subscription =
Observable
.Interval(TimeSpan.FromMilliseconds(250.0))
.Select(x => String.Format("{0:00}", x))
.ObserveOnDispatcher()
.Subscribe(x => MinuteTimerLabel.Content = _counter++ % 100);
}
private void MinuteTimerLabel_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
_subscription.Dispose();
}
Try the below code:
The code will run in the another thread un-blocking the Main Thread.
private void MinuteTimerLabel_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
new Thread(Timer).Start();
}
private void Timer() {
while(true)
{
if (timerMinute == 99)
{
return;
}
timerMinute += 1;
minuteTimerLabel.Content = String.Format("{0:00}", timerMinute);
Thread.Sleep(250);
}
}
Good fit for a CancellationToken and Async Await Pattern
private CancellationTokenSource _cs;
private int _timerMinute =0;
private async void Label1_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_cs?.Cancel();
_cs?.Dispose();
_cs = new CancellationTokenSource();
try
{
while (!_cs.Token.IsCancellationRequested)
{
await Task.Delay(200, _cs.Token);
Label1.Content = $"{++_timerMinute:00}";
}
}
catch (OperationCanceledException) {}
}
private void Label1_OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
_cs?.Cancel();
_cs?.Dispose();
}
Also if you wanted to be tricky you could add weight to it the more the mouse is down, by altering the delay
You need to bind couple of event in this case, PreviewMouseDown and PreviewMouseUp. Then calculate time different between it and increment the value for label on ticks/secs.
Like this
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
PreviewMouseDown += Window3_PreviewMouseDown;
PreviewMouseUp += Window3_PreviewMouseUp;
}
DateTime mouseDown;
private void Window3_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
mouseDown = DateTime.Now;
}
private void Window3_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
var seconds = DateTime.Now.Subtract( mouseDown ).Seconds;
//Condition code goes here..
minuteTimerLabel.Content = seconds;
}
}
In my program i'm starting for loop using button, I want to break this for loop using another button.
For example:
private void button1_Click(object sender, EventArgs e)
{
for( int i = 0; i < var; i++)
{
//doing something
}
}
And using second button break loop,
private void button2_Click(object sender, EventArgs e)
{
//breaking loop;
}
Need help :)
Set a flag in button2_Click() method and check it in the button1_Click()'s loop.
In order to process Windows events and allow button2_Click() handle to run while iterating, add Application.DoEvents() in your loop:
bool breakLoop = false;
private void button1_Click(object sender, EventArgs e)
{
breakLoop = false;
for( int i = 0; i < var && !breakLoop; i++)
{
//doing something
Application.DoEvents();
}
}
private void button2_Click(object sender, EventArgs e)
{
breakLoop = true;
}
You cannot do that, because the loop in button1_Click event handler will be holding the UI thread. Your user interface will not respond to any event, showing hourglass icon, until the loop is over. This means that button2_Click cannot be entered until button1_Click has completed.
You need to replace the long-running loop from the event handler with something that runs outside the UI thread. For example, you can use Tasks, which can be cancelled using CancellationToken (related Q&A).
Arguably it would be better to use threads and cancellation tokens in some form, rather than the Application.DoEvents(). Something like this:
private CancellationTokenSource loopCanceller = new CancellationTokenSource();
private void button1_Click(object sender, EventArgs e)
{
Task.Factory.StartNew(() =>
{
try
{
for (int i = 0; i < 100; i++)
{
this.loopCanceller.Token.ThrowIfCancellationRequested(); // exit, if cancelled
// simulating half a second of work
Thread.Sleep(500);
// UI update, Invoke needed because we are in another thread
Invoke((Action)(() => this.Text = "Iteration " + i));
}
}
catch (OperationCanceledException ex)
{
loopCanceller = new CancellationTokenSource(); // resetting the canceller
Invoke((Action)(() => this.Text = "Thread cancelled"));
}
}, loopCanceller.Token);
}
private void button2_Click(object sender, EventArgs e)
{
loopCanceller.Cancel();
}
I tried to add something to log inside WinForm while doind something
private async Task SaveChunk(DataChunkSaver chunk)
{
int i = 0;
int step = 10;
while (chunk.saveChunk(i, step))
{
i += step;
AddLog(chunk.Log);
}
}
where:
private async Task AddLog(string text)
{
LogBulider.AppendLine(text);
LogBox.Text = LogBulider.ToString();
}
AndLogBulider is a simple global StringBulider.
The problem is when I fire button with SaveChunk task my form freezes, so I can see the LogBox after everything is done and I wanned it to bisplayed after each step of chunk.saveChunk.
I tried to fire them by few methods, but I can't handle it
What Am I doing wrong?
private async void button2_Click(object sender, EventArgs e)
{
await Task.Factory.StartNew(() => SaveChunk(chunk));
Task T = SaveChunk(chunk);
// none of these works, I also tried few other
//ways to do it, but none prevents my winForm from freezing
}
I tried to modify your code using a Progress<string>:
private async void button2_Click(object sender, EventArgs e)
{
var progress = new Progress<string>(msg =>
{
LogBulider.AppendLine(msg);
LogBox.Text = LogBulider.ToString();
});
await Task.Run(() => SaveChunk(chunk, progress));
}
and
private async Task SaveChunk(DataChunkSaver chunk, IProgress<string> progress)
{
int i = 0;
int step = 10;
while (chunk.saveChunk(i, step))
{
i += step;
progress?.Report(chunk.Log); // Always use progress as if it could be null!
}
}
I have the following code to update the progress bar in async fashion and i notice
its async behaviour through the call to MessageBox.In this case it works perfectly
but when i give a sleep of 1s(1000) the MessageBox doesnot pops up and the the complete progress bar fills at once.
Kindly tell why this is happening.
private void button1_Click(object sender, EventArgs e)
{
Update_Async async = new Update_Async(Update_Async_method);
progressBar1.BeginInvoke(async,10);
MessageBox.Show("Updation In Progress");
}
public void Update_Async_method(int a)
{
this.progressBar1.Maximum = a;
for (int i = 1; i <= a; i++)
{
progressBar1.Value = a;
Thread.Sleep(10);
//Thread.Sleep(1000);
}
}
Try Update_Async.BeginInvoke(async, 10) instead if you want the delegate to run asynchrnously but, you'll have to cross thread checking on the update to the progress bar.
In response to your comment, very similar to what you are doing already,
void UpdatingFunction(int value)
{
if (this.progressBar.InvokeRequired)
{
this.progressBar.BeginInvoke(UpdatingFunction, value);
return;
}
// Invoke not required, work on progressbar.
}
This also explains what the Invoke methods on controls are for.
Delegate.BeginInvoke will run a method in a thread once and then dispose it. It is a poor choice if you want to repeatedly do some work in a thread and return intermediate results. If that is what you want, you should use BackgroundWorker. Highly abbreviated snippet:
BackgroundWorker bw;
YourFormConstructor()
{
...
bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.DoWork += BackgroundCalculations;
bw.ProgressChanged += ShowBackgroundProgress;
}
private void button1_Click(object sender, EventArgs e)
{
bw.RunWorkerAsync(10);
}
void ShowBackgroundProgress(object sender, ProgressChangedEventArgs e)
{
this.progressBar.Value = e.ProgressPercentage;
}
static void BackgroundCalculations(object sender, DoWorkEventArgs e)
{
BackgroundWorker bw = sender as BackgroundWorker;
int max = (int)e.Argument;
for (int i = 0; i < max; i++)
{
bw.ReportProgress(i * 100 / max);
Thread.Sleep(10);
}
bw.ReportProgress(100);
}
}
I am creating some files from xml data in the background using
Task.Factory.StartNew(() => xmlconvert(xx, yy));
Now, the question is how to show the progress of this method using a StatusStrip control with some message and the progress or at least just a scrolling animation for the progress. I don't just have any idea how would it work.
Update:
First of all, this method 'xmlconvert(xx, yy)' has four different forms depends on the condition user selects at runtime.
In the main form of my application user can select from different conditions to process on the data. Then finally when user click on the Button 'Create' all these conditions are being checked and a suitable method will be called within that button click event. I need to show the progress of this method which is being invoked at runtime.
private void btnCreateRelease_Click(object sender, EventArgs e)
{
// Checks set of conditions
if(cond 1)
{
xmlconvert_1();
}
else if (cond2)
{
xmlconvert_2();
}
else if (cond3)
{
xmlconvert_3();
}
else if (cond4)
{
xmlconvert_4();
}
}
I want to show progress of one of these methods which will be invoked at runtime depends on the condition.
Thanks a lot.
You can use the BackgroundWorker for this, and it's pretty simple, too. Here's a sample to get you going:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
}
void Form1_Shown(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// Do your work in here.
xmlconvert(xx, yy);
for (int i = 0; i <= 100; i++)
{
backgroundWorker1.ReportProgress(i);
System.Threading.Thread.Sleep(100);
}
}
void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
}
And here's the link to the documentation.
To get it to work in your scenario, I would suggest you add a Progress bar to your StatusStrip control and update it from within the backgroundWorker1_ProgressChanged event.
If you wish just to show, that your app is not hang may help following approach:
public static class ActionExtensions
{
public static void RunWithMargueProgress(this Action action)
{
var progressForm = new ProgressForm();
progressForm.Show();
Task.Factory.StartNew(action)
.ContinueWith(t =>
{
progressForm.Close();
progressForm.Dispose();
}, TaskScheduler.FromCurrentSynchronizationContext());
}
}
Where ProgressForm would be a simple form with ProgressBar, that is set to Marquee style. If you have idea, how it is progressing, it is better to show progress for user and use BackgroundWorker.
As long as it's parameter is Action, it is easily reusable.
Usage would be:
private void button_Click(object sender, EventArgs e)
{
Action action = () => Thread.Sleep(5000);
action.RunWithMargueProgress();
}
If you have control in status strip, that you wish to animate, you can do it like this:
public static void RunWithMargueProgress(this Action action, ToolStripProgressBar progressBar)
{
progressBar.Style = ProgressBarStyle.Marquee;
progressBar.MarqueeAnimationSpeed = 30;
Task.Factory.StartNew(action)
.ContinueWith(t =>
{
progressBar.MarqueeAnimationSpeed = 0;
progressBar.Style = ProgressBarStyle.Continuous;
}, TaskScheduler.FromCurrentSynchronizationContext());
}
Usage would be pretty much the same:
private void button_Click(object sender, EventArgs e)
{
Action action = () => Thread.Sleep(5000);
action.RunWithMargueProgress(ToolStripProgressBar);
}