How can i pause a method execution or the current iteration until the user press next button for example?
I want an efficient way because i can't divide the method into other methods and I don't want to use Thread.Sleep() because it freezes the GUI.
public void Calc(int x)
{
while(x < 4)
{
//My Work
textbox1.Text += "Press next to continue";
//Need to pause the iteration until taking a signal from a button
}
}
void button1(...)
{
Calc(1);
}
Use SemaphoreSlim
You can run both work and button code on UI thread, thanks to async/await scheduling. And you can reuse same semaphore instance to make multiple signals.
//not signaled semaphore with maximum of 1 signal
SemaphoreSlim _workSignal = new SemaphoreSlim(0,1);
Your work code:
async void DoWork()
{
//Do Something
//this tries to decrease signal count and if signal count is 0,
//waits until it will have some signals, then "takes"
//one signal to go through.
//After this line the semaphore will be in non-signaled state
await _workSignal.WaitAsync();
//Do more
}
Your button handler
void Button_Click(...)
{
_signal.Release();//increases signal count, allowing your work code to go through
}
I think a simple ManualResetEvent along with async method may help you:
public partial class Form1 : Form
{
private ManualResetEvent _calcEvent;
private delegate void ChangeTextMethod(string text);
private ChangeTextMethod _changeTextHandler;
public Form1()
{
InitializeComponent();
_calcEvent = new ManualResetEvent(false);
_changeTextHandler = delegate (string text) {
textbox1.Text += text;
};
}
private void button1_Click(object sender, EventArgs e)
{
Calc(1);
}
public async Task Calc(int x)
{
await Task.Run(() => {
while (x < 4)
{
//My Work
textbox1.Invoke(_changeTextHandler, "Press next to continue");
//Need to pause the iteration until taking a signal from a button
_calcEvent.WaitOne();
x = 4;
}
textbox1.Invoke(_changeTextHandler, "... continued");
});
}
private void button2_Click(object sender, EventArgs e)
{
_calcEvent.Set();
}
}
Related
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!
}
}
This is my code:
private void _1_Load(object sender, EventArgs e)
{
FillA();
FillB();
// Tried these lines too but they show listbox values but not textbox value
// var t2 = Task.Run(() => FillA());
// FillB();
}
private void FillA()
{
this.Invoke(new MethodInvoker(delegate()
{
for (int i = 1; i > 0; i++)
{
listBox1.Items.Add(i.ToString());
listBox1.Update();
Thread.Sleep(25);
}
}));
}
private void FillB()
{
Thread.Sleep(3000); //1 seconds delay
textBox1.Text = "Hello World!";
}
When the page loads the listbox should starts showing the number 1, 2, 3... and then after few seconds of delay the textbox should show the Hello World! value but the listbox should keep growing. How do I ensure that first the listbox should start filling and then the textbox should fill. I don't want to wait for the filling of the listbox and then textbox to be filled.
Any advice would be appreciated.
Edit:
This is just a sample code. In actual production there would be two processes and the second process will start once the first process is started fetching data and storing in listbox.
The first process should keep on going. I can't stop it in middle as it would create inconsistency in the data.
Don't use Sleep. Instead use Timer-class:
https://msdn.microsoft.com/en-us/library/system.timers.timer(v=vs.110).aspx
Configure a timeout of 1s.
Hook up the Elapsed event for the timer:
aTimer.Elapsed += OnTimedEvent;
In method OnTimedEvent you can increment a integer-variable i.
Depending on value of i you fill first listbox and if value is higher fill the other Listbox.
You cannot add all items at once to the listbox and update the textbox in between as these operations are done synchronously in WindowsForms.
Instead you should add only a subset of items to the listbox, update the textbox and then continue adding items to the listbox.
To not freeze the GUI in between waiting for operations, you can use async\await combination instead of Thread.Sleep().
private async void _1_Load(object sender, EventArgs e)
{
await AddInitialListBoxItems();
await FillTextBox();
await AddRemainingListBoxItems();
}
private async Task AddInitialListBoxItems()
{
for (int i = 1; i < 10; i++)
{
listBox1.Items.Add(i.ToString());
listBox1.Update();
await Task.Delay(25);
}
}
private async Task FillTextBox()
{
await Task.Delay(3000); //1 seconds delay
textBox1.Text = "Hello World!";
}
private async Task AddRemainingListBoxItems()
{
for (int i = 11; i < 100; i++)
{
listBox1.Items.Add(i.ToString());
listBox1.Update();
await Task.Delay(25);
}
}
If you can use Timers instead of direct threading, this might help you. It worked on my pc:
Edit since you need the textbox to get written after some delay, you need to use a timer for it as well. I have edited the code below. Textbox is written after 1 seconds of listbox population starting.
public partial class Form1 : Form
{
int counter = 0;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
timer.Interval = 25;
timer.Tick += Timer_Tick;
timer.Start();
System.Windows.Forms.Timer timerTextbox = new System.Windows.Forms.Timer();
timerTextbox.Interval = 1000;
timerTextbox.Tick += TimerTextbox_Tick; ;
timerTextbox.Start();
}
private void TimerTextbox_Tick(object sender, EventArgs e)
{
FillB();
(sender as System.Windows.Forms.Timer).Stop();
}
private void Timer_Tick(object sender, EventArgs e)
{
listBox1.Items.Add(counter.ToString());
counter++;
listBox1.Update();
}
private void FillB()
{
textBox1.Text = "Hello World!";
}
}
Of course when you call Thread.Sleep(3000); //1 seconds delay on Load event (3000 ms is 3 seconds btw), your form will be shown 3 seconds late.
In My application have time consuming process.There fore i try to do that operation in separate thread.Even i Stared it separate thread my Main UI still freezes during the time of long running process.But still i couldn't figure out the reason for that?Some thing wrong in my code?
My Event Hander Code
private void BtnloadClick(object sender, EventArgs e)
{
if (null != cmbSource.SelectedItem)
{
string selectedITem = ((FeedSource) cmbSource.SelectedItem).Url;
if (!string.IsNullOrEmpty(selectedITem))
{
Thread starter = new Thread(() => BindDataUI(selectedITem));
starter.IsBackground = true;
starter.Start();
}
}
private void BindDataUI(string url)
{
if (feedGridView1.InvokeRequired)
{
BeginInvoke(new Action(() => BindDataGrid(url)));
}
else
BindDataGrid(ss);
}
private void BindDataGrid(string selectedItem)
{
for (int i = 0; i < 10; i++)
{
//Time consuming Process
}
}
Your thread is completely useless :-)
In your thread you are executing BindDataUI which marshals the execution back to the UI thread using Invoke.
Your complete code is equivalent to this:
private void BtnloadClick(object sender, EventArgs e)
{
if (null != cmbSource.SelectedItem)
{
string selectedITem = ((FeedSource) cmbSource.SelectedItem).Url;
if (!string.IsNullOrEmpty(selectedITem))
{
BindDataGrid(selectedITem);
}
}
private void BindDataGrid(string selectedItem)
{
for (int i = 0; i < 10; i++)
{
//Time consuming Process
}
}
It would be better to only marshal these parts of BindDataGrid to the UI thread that really need to run on this thread because they need to update the UI.
I wanted to make a simple Countdown-Application with C# to show as an example.
For the very first and basic version I use a Label to display the current time left in seconds and a Button to start the countdown. The Button's Click-Event is implemented like this:
private void ButtonStart_Click(object sender, RoutedEventArgs e)
{
_time = 60;
while (_time > 0)
{
_time--;
this.labelTime.Content = _time + "s";
System.Threading.Thread.Sleep(1000);
}
}
Now when the user clicks the Button the time is actually counted down (as the application freezes (due to Sleep())) for the chosen amount of time but the Label's context is not refreshed.
Am I doing something generally wrong (when it comes to Threads) or is it just a problem with the UI?
Thank you for your answers!
I now use a System.Windows.Threading.DispatcherTimer to do as you told me. Everything works fine so this question is officially answered ;)
For those who are interested: Here is my code (the essential parts)
public partial class WindowCountdown : Window
{
private int _time;
private DispatcherTimer _countdownTimer;
public WindowCountdown()
{
InitializeComponent();
_countdownTimer = new DispatcherTimer();
_countdownTimer.Interval = new TimeSpan(0,0,1);
_countdownTimer.Tick += new EventHandler(CountdownTimerStep);
}
private void ButtonStart_Click(object sender, RoutedEventArgs e)
{
_time = 10;
_countdownTimer.Start();
}
private void CountdownTimerStep(object sender, EventArgs e)
{
if (_time > 0)
{
_time--;
this.labelTime.Content = _time + "s";
}
else
_countdownTimer.Stop();
}
}
Yes, event handlers should not block - they should return immediately.
You should implement this by a Timer, BackgroundWorker or Thread (in this order of preference).
What you are seeing is the effect of a long-running message blocking the windows message queue/pump - which you more commonly associate with the white application screen and "not responding". Basically, if your thread is sleeping, it isn't responding to messages like "paint yourself". You need to make your change and yield control to the pump.
There are various ways of doing this (ripper234 does a good job of listing them). The bad way you'll often see is:
{ // your count/sleep loop
// bad code - don't do this:
Application.DoEvents();
System.Threading.Thread.Sleep(1000);
}
I mention this only to highlight what not to do; this causes a lot of problems with "re-entrancy" and general code management. A better way is simply to use a Timer, or for more complex code, a BackgroundWorker. Something like:
using System;
using System.Windows.Forms;
class MyForm : Form {
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Application.Run(new MyForm());
}
Timer timer;
MyForm() {
timer = new Timer();
count = 10;
timer.Interval = 1000;
timer.Tick += timer_Tick;
timer.Start();
}
protected override void Dispose(bool disposing) {
if (disposing) {
timer.Dispose();
}
base.Dispose(disposing);
}
int count;
void timer_Tick(object sender, EventArgs e) {
Text = "Wait for " + count + " seconds...";
count--;
if (count == 0)
{
timer.Stop();
}
}
}