I have two timers (System.Windows.Forms.Timer) in a WinForms application.
Both timers start at the same time. One timer starts and stops throughout the life of the program updating three labels, the other just runs and does its work at every tick event updating one label. However, when the first timer is running code in its tick event the second timer isn't running.
In the first timer tick event code I have inserted multiple System.Threading.Thread.Yield(); statements, but the second timer is still being blocked. Searches for this comes up null.
I tried using system threading for the second timer, but it isn't doing anything.
I'm at a loss.
Any ideas?
public partial class fMain2 : Form
{
private System.Windows.Forms.Timer timer;
private System.Windows.Forms.Timer timer2;
private Thread tThread;
private int totTime;
private int curTime;
private int exTime = 0;
public int runTime = 0;
private void cmdRun_Click(object sender, EventArgs e)
{
//calculate total time
totTime = iCount * iDuration;
lblTotTime.Text = totTime.ToString();
lblTotEx.Text = exTime.ToString();
System.Threading.Thread.Yield();
curTime = int.Parse("0" + txtDuration.Text);
System.Threading.Thread.Yield();
this.Refresh();
strFile = "Begin" + ".wav";
snd.SoundLocation = strSoundFilePath + strFile;
snd.PlaySync();
//select first item in the listview
lvTimer.Items[0].Selected = true;
lvi = lvTimer.Items[0];
lvTimer.Refresh();
strFile = lvi.SubItems[1].Text + ".wav";
snd.SoundLocation = strSoundFilePath + strFile;
snd.PlaySync();
System.Threading.Thread.Yield();
strFile = "Go" + ".wav";
snd.SoundLocation = strSoundFilePath + strFile;
snd.PlaySync();
//attempted using a thread for timer2
timer.Start();
//tThread = new Thread(new ThreadStart(timer2.Start));
timer2.Start();
//tThread.Start();
}
private void timerTick(object sender, EventArgs e)
{
string strFile;
curTime -= 1;
totTime -= 1;
exTime += 1;
System.Threading.Thread.Yield();
lblCurTime.Text = curTime.ToString();
lblTotTime.Text = totTime.ToString();
lblTotEx.Text = exTime.ToString();
this.Refresh();
System.Threading.Thread.Yield();
if (curTime == 0)
{
timer.Stop();
System.Threading.Thread.Yield();
System.Threading.Thread.Yield();
strFile = "Stop" + ".wav";
snd.SoundLocation = strSoundFilePath + strFile;
snd.PlaySync();
System.Threading.Thread.Yield();
System.Threading.Thread.Yield();
if (totTime == 0)
{
//this marks the end of the program
timer2.Stop();
//tThread.Abort();
//more code but not relevant
return;
}
else
{ //we are still working down the listview
try
{
lvi = lvTimer.Items[lvi.Index + 1];
lvTimer.Items[lvi.Index].Selected = true;
lvTimer.FocusedItem = lvTimer.Items[lvi.Index];
lvTimer.Refresh();
System.Threading.Thread.Yield();
System.Threading.Thread.Yield();
System.Threading.Thread.Yield();
}
catch (IndexOutOfRangeException ei)
{
strFile = "End" + ".wav";
snd.SoundLocation = strSoundFilePath + strFile;
snd.PlaySync();
bRunning = false;
ResetTime();
return;
}
curTime = int.Parse("0" + txtDuration.Text);
lblCurTime.Text = curTime.ToString();
lblTotTime.Text = totTime.ToString();
System.Threading.Thread.Yield();
System.Threading.Thread.Yield();
//I'm wondering if the soundplayer is causing the problem
strFile = lvi.SubItems[1].Text + ".wav";
snd.SoundLocation = strSoundFilePath + strFile;
System.Threading.Thread.Yield();
System.Threading.Thread.Yield();
snd.PlaySync();
System.Threading.Thread.Yield();
System.Threading.Thread.Yield();
strFile = "Go" + ".wav";
snd.SoundLocation = strSoundFilePath + strFile;
snd.PlaySync();
System.Threading.Thread.Yield();
System.Threading.Thread.Yield();
System.Threading.Thread.Yield();
timer.Start();
}
}
}
private void timer2Tick(object sender, EventArgs e)
{
//this is all timer2 does. It runs as long as the
// program is running.
runTime += 1;
lblTotTotal.Text = (runTime / 60).ToString()
+ ":" + (runTime % 60).ToString("00");
}
}
I am using VS 2017.
System.Windows.Forms.Timer is designed to run code on the UI thread. Calling System.Threading.Thread.Yield() tells the system to run another thread that is ready to run on the current core, but your timers want to run on the UI thread so it doesn't help in any way.
It seems to me that you're blocking the UI thread playing your sound with .PlaySync().
I'm going to suggest that you push the playing of the sound to a background thread to free up the UI.
From what I can gather from your code, you're trying to play a series of sounds like this:
"Begin.wav", then "ListItem1.wav", then"Go.wav"
(wait a period of time)
"ListItem2.wav", then "Go.wav"
(wait a period of time)
"ListItem3.wav", then "Go.wav"
(wait a period of time)
"End.wav"
However, if a timer runs out then cancel playing these sounds and play a "Stop.wav" sound instead.
This structure can be modelled with a Queue<Queue<string>> and you just need to a nested dequeue and pay all of the sounds.
Queue<Queue<string>> queue =
new Queue<Queue<string>>(new[]
{
new Queue<string>(new[] { "Begin.wav", "ListItem1.wav", "Go.wav" }),
new Queue<string>(new[] { "ListItem2.wav", "Go.wav" }),
new Queue<string>(new[] { "ListItem3.wav", "Go.wav" }),
new Queue<string>(new[] { "End.wav" }),
});
Here's the code to dequeue the queues:
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken ct = cts.Token;
Task task = Task.Run(async () =>
{
while (queue.Any())
{
if (ct.IsCancellationRequested)
{
break;
}
Queue<string> inner = queue.Dequeue();
while (inner.Any())
{
if (ct.IsCancellationRequested)
{
break;
}
string soundLocation = inner.Dequeue();
using (System.Media.SoundPlayer sp = new System.Media.SoundPlayer())
{
sp.SoundLocation = soundLocation;
sp.PlaySync();
}
}
if (ct.IsCancellationRequested)
{
using (System.Media.SoundPlayer sp = new System.Media.SoundPlayer())
{
sp.SoundLocation = soundLocationStop;
sp.PlaySync();
}
}
else
{
await Task.Delay(TimeSpan.FromSeconds(2.0));
}
}
});
Note that this is all run in a Task.Run so it's not blocking the UI thread.
To stop the processing of the sound, just call cts.Cancel();.
You now just need to build your queue in the cmdRun_Click method.
private void cmdRun_Click(object sender, EventArgs e)
{
string[][] soundLocationGroups =
(
from x in lvTimer.Items.Cast<ListViewItem>()
from y in x.SubItems.Cast<ListViewItem.ListViewSubItem>()
select new[] { Path.Combine(strSoundFilePath, $"{y.Text}.wav"), Path.Combine(strSoundFilePath, $"Go.wav") }
).ToArray();
soundLocationGroups =
soundLocationGroups
.Take(1)
.Select(xs => xs.Prepend(Path.Combine(strSoundFilePath, $"Begin.wav")).ToArray())
.Concat(soundLocationGroups.Skip(1))
.Append(new[] { Path.Combine(strSoundFilePath, $"End.wav") })
.ToArray();
string soundLocationStop = Path.Combine(strSoundFilePath, $"Stop.wav");
Queue<Queue<string>> queue = new Queue<Queue<string>>(soundLocationGroups.Select(x => new Queue<string>(x)));
You still need a single timer to know if you should call cts.Cancel().
I did test my Task.Run code before posting. It works.
My suggestion is to use async/await.
Step 1: Get rid of all the System.Threading.Thread.Yield(); calls.
Step 2: Get rid of all the .Refresh(); calls.
Step 3: Make the timerTick async: private async void timerTick(object sender, EventArgs e)
Step 4: Replace every occurrence of snd.PlaySync(); with await Task.Run(() => snd.PlaySync());
The Task.Run method invokes the specified lambda on the ThreadPool. The await allows the current thread to continue doing other things, like responding to user input, while the lambda is running on the ThreadPool. After the lambda completes, the current thread continues executing the code that follows the await, until the next await, or until the end of the handler.
After doing these changes the UI should be responsive again, and the other timer should be ticking normally every second.
Be aware that by making the timerTick handler async, it is now possible to be invoked in an overlapping manner. It's up to you to prevent the overlapping, by checking and updating fields of the form.
Related
I have a ProgressChanged event for download and in this event I update a Listview writing async threads statuses. But this operation slows down the program, even causes Not Responding. My code and an ss are below. How can I fix this problem.
private void downloader_ProgressChanged(object sender, EventArguments.ProgressChangedEventArgs e)
{
this.Invoke(new Action(() =>
{
progressBar1.Value = (int)(downloader.Progress * 100);
if (downloader.Info.ContentSize > 0)
lblContentSize.Text = downloader.Info.ContentSize.ToHumanReadableSize();
lblSpeed.Text = downloader.Speed.ToHumanReadableSize() + "/s";
lblReceived.Text =
string.Format("{0} ({1})",
downloader.TotalBytesReceived.ToHumanReadableSize(),
string.Format("{0:0.00}%", downloader.Progress));
segmentedProgressBar1.ContentLength = downloader.Info.ContentSize;
segmentedProgressBar1.Bars = downloader.Ranges.ToList().Select(x => new Bar(x.TotalBytesReceived, x.Start, x.Status)).ToArray();
lblResumeability.Text = downloader.Info.AcceptRanges ? "Yes" : "No";
listView1.BeginUpdate();
writeThreads();
listView1.EndUpdate();
}));
}
private void writeThreads()
{
var ranges = downloader.Ranges.ToList();
ranges = ranges.Where(x => !x.IsIdle).ToList();
for (var i = 0; i < ranges.Count; i++)
{
listView1.Items[i].SubItems[1].Text = (ranges[i].TotalBytesReceived.ToHumanReadableSize());
listView1.Items[i].SubItems[2].Text = (ranges[i].Status.ToString());
}
}
DownloadProgressChanged being fired on every chunk received e.g. for each 4kb received. You probably may update UI not for each call but only if percentage was changed.
private int storedPercentage = -1;
private void downloader_ProgressChanged(object sender, EventArguments.ProgressChangedEventArgs e)
{
if (e.ProgressPercentage != storedPercentage)
{
storedPercentage = e.ProgressPercentage;
this.Invoke((Action)(() =>
{
// existing code...
}));
}
}
Disclamer: exactly this way to fix is applicable only for single active download process. But the idea would be the same for concurrent downloads.
I want to put a function on an endless loop but with a timeout countdown. After each time it perform its function, it should stop for a manually-configured amount of time. If I use Sleep, it freezes everything else. Is there a way that I can do this without affecting the other functions in my project?
private void btn30_Click(object sender, EventArgs e)
{
int hour = DateTime.Now.Hour;
int minute = DateTime.Now.Minute;
int second;
do
{
if (5 + DateTime.Now.Second > 60)
{
second = (DateTime.Now.Second + 5) - 60;
}
else if (5 + DateTime.Now.Second == 60)
{
second = 0;
}
else
{
second = DateTime.Now.Second + 5;
}
if (sc.checkScheduleStarted() == false)
{
sc.Start30(hour, minute, second);
btn30.Text = "5 second waiting";
System.Threading.Thread.Sleep(5000);
}
else
{
sc.Stop();
btn30.Text = "Countdown - 5";
}
} while (loopCycle == true);
}
You're using Task.Sleep() on the main UI thread. Obviously you'll end up freezing the UI.
You should consider running your calculations in background with Task.Run and then make delays with non-blocking thread Task.Delay:
private void btn30_Click(object sender, EventArgs e)
{
Task.Run(async() =>
{
do
{
// Your stuff here...
// Pick one of the two examples below for your case:
// 1. Updates the UI of a WPF application
Application.Current.Dispatcher.Invoke(() =>
{
// Update the UI here...
});
// 2. Updates the UI of a WinForm application
Invoke(new Action(() =>
{
// Update the UI here...
}));
// Make a delay...
await Task.Delay(5000);
} while (true);
});
}
Note that when you want to update the UI from a background thread you have to somehow execute your code that changes the UI in the main thread (the one that created controls you're updating).
I would recommend:
For WPF - Dispatcher.Invoke
For WinForms - Control.Invoke
Don't bother with threading. Just drop a Timer control on the form and subscribe to the tick event. The control will call the event within the UI thread and won't freeze the ui.
public class YourForm {
private int Seconds = 5;
private void btn30_Click(object sender, EventArgs e) {
YourTimer.Start();
}
public void YourTimer_Tick(object sender, EventArgs e)
{
Seconds--;
if (Seconds<= 0) {
YourTimer.Stop();
// Do your stuff
}
}
}
Reference : Timer class
Make your click handler asynchronous, and use Task.Delay instead of Thread.Sleep(). This will allow the handler to release control to the calling context and allow it to schedule other tasks while waiting for the Delay to pass.
private async Task btn30_Click(object sender, EventArgs e)
{
int hour = DateTime.Now.Hour;
int minute = DateTime.Now.Minute;
int second;
do
{
if (5 + DateTime.Now.Second > 60)
{
second = (DateTime.Now.Second + 5) - 60;
}
else if (5 + DateTime.Now.Second == 60)
{
second = 0;
}
else
{
second = DateTime.Now.Second + 5;
}
if (sc.checkScheduleStarted() == false)
{
sc.Start30(hour, minute, second);
btn30.Text = "5 second waiting";
await Task.Delay(5000);
//Rest of code
Trying to update a UI element that updates as DoWork() iterates through every line in a DataTable with a visual progress bar and a textbox that gives the current value. The iteration happens in a background worker and works as expected, as does the progress bar's PerformStep(). However, the TextBox's Text does not change.
void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
table = SegmentationLibrary.Core.Utils.GetTableFromQuery(query);
backgroundWorker1.ReportProgress(table.Rows.Count, "Max");
int status = 0;
backgroundWorker1.ReportProgress(status);
table.Columns.Add("seg_rates_id", typeof(Int32));
foreach (DataRow row in table.Rows)
{
row["seg_rates_id"] = 0;
status++;
backgroundWorker1.ReportProgress(status);
Application.DoEvents();
}
}
Progress:
void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (e.UserState != null && e.UserState.ToString() == "Max") {
progressBar.progressBar1.Maximum = e.ProgressPercentage;
}
else if (e.ProgressPercentage == 0)
{
progressBar.progressBar1.Minimum = 0;
progressBar.progressBar1.Style = ProgressBarStyle.Continuous;
progressBar.progressBar1.Value = 1;
progressBar.progressBar1.Step = 1;
progressBar.tbProgress.Text = "Setting initial rate types";
}
else
{
progressBar.progressBar1.PerformStep();
progressBar.tbProgress.Text = "Current at row " + progressBar.progressBar1.Value + " of " + progressBar.progressBar1.Maximum + ", " + progressBar.GetProgressBarPercent().ToString() + "%.";
}
}
Caller:
public void GetFinacData(int year, int month)
{
if (!backgroundWorker1.IsBusy)
{
Application.EnableVisualStyles();
currentMonth = month;
currentYear = year;
query = "SELECT * FROM FINAC_ACTUAL_DATA WHERE fiscal_year = " +
currentYear + " AND fiscal_month = " + currentMonth + ";";
progressBar.Show();
progressBar.progressBarStyle = ProgressBarStyle.Marquee;
progressBar.tbProgress.Text = "Downloading PSGL Extract from database";
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
backgroundWorker1.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;
backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
backgroundWorker1.RunWorkerAsync();
// required for ProgressBar to update?
while (backgroundWorker1.IsBusy)
Application.DoEvents();
workerCompleted.WaitOne();
progressBar.Hide();
}
else
{
MessageBox.Show("Segmentation already in progress.","Error",MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
What's happening:
I tried a few different things, but none of them work. Adding another DoEvents() at the end of ProgressChanged causes a stack overflow. Any ideas?
you could wrap you ui changing code inside this code
this.Invoke(new Action(() => { /* ui changing code */ }));
You can do the updating from the background work, not from your main thread. At this point your main thread is still working and your updates are not processed.
remove
// required for ProgressBar to update?
while (backgroundWorker1.IsBusy)
Application.DoEvents();
workerCompleted.WaitOne();
progressBar.Hide();
then move the:
progressBar.Hide();
to the end of your background worker. And remove all DoEvents from your code. It is not needed to do the updates.
I have an application that periodically pings a server to give you the latency of your connection. From what I have read the System.Timers.Timer is run on a separate thread from the UI.
public MainForm()
{
InitializeComponent();
_timer = new Timer();
_timer.Tick += new EventHandler(timer_Tick);
_timer.Interval = 1000;
_timer.Start();
}
I have a NotifyIcon with a ContextMenu that displays the result of this ping. however I noticed that the ContextMenu lags every time the timer ticks and I have no idea why.
EDIT: completely forgot to add the timer_tick function
var selectedServer = Properties.Settings.Default.SelectedServer;
PingReply reply;
switch (selectedServer)
{
case "NA":
reply = _pvpnetClient.Send("64.7.194.1");
break;
case "EUW":
reply = _pvpnetClient.Send("95.172.65.1");
break;
case "EUN":
reply = _pvpnetClient.Send("66.150.148.1");
break;
default:
reply = _pvpnetClient.Send("64.7.194.1");
break;
}
if (reply == null || reply.Status != IPStatus.Success) return;
var returnedPing = reply.RoundtripTime;
LoLPing.Text = #"Server: " + selectedServer + #" - Ping: " + reply.RoundtripTime + #"ms";
PingText.Text = #"Ping: " + reply.RoundtripTime + #"ms";
if (returnedPing < 120f)
{
LoLPing.Icon = Properties.Resources.GreenIcon;
}
else if (returnedPing > 120f && returnedPing < 200)
{
LoLPing.Icon = Properties.Resources.YellowIcon;
}
else
{
LoLPing.Icon = Properties.Resources.RedIcon;
}
http://msdn.microsoft.com/en-us/library/system.timers.timer(v=vs.100).aspx
"The server-based Timer is designed for use with worker threads in a multithreaded environment."
It doesn't automatically generate it's own thread. If it did generate it's own thread, you'd get an exception when trying to update your control without the use of Invoke or BeginInvoke.
I would do this with a BackgroundWorker object, who's DoWork handler contained a loop with a Thread.Sleep in it. Then do all the slow ping work there in the DoWork loop, and have one GUI function that takes returnedPing and does the icon update. Call this GUI function with Invoke to get the GUI actions back on the GUI thread.
class SlowOperation
{
BackgroundWorker m_worker;
void StartPolling()
{
m_worker = new BackgroundWorker();
m_worker.WorkerSupportsCancellation = true;
m_worker.WorkerReportsProgress = true;
m_worker.DoWork += m_worker_DoWork;
m_worker.ProgressChanged += m_worker_ProgressChanged;
}
void m_worker_DoWork(object sender, DoWorkEventArgs e)
{
while (!m_worker.CancellationPending)
{
int returnedPing = 0;
// Get my data
m_worker.ReportProgress(0, returnedPing);
Thread.Sleep(1000);
}
}
void m_worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
myForm.Invoke(myForm.UpdateMyPing((int)e.UserState));
}
}
It all depends on how long the Send method of _pvpnetClient takes to run.
You are running a System.Windows.Timer (I know because of the Tick event), so this method will be invoked on the main thread, and it will block all GUI updates until it is done. If you want to do the work on another thread, you could use the System.Timers.Timer object, but you would have to invoke back to the main thread to update the GUI elements.
I'm trying to create a Windows Form application that searches for a string and has three possible scenarios:
String 1 found - wait
String 2 found - stop
Else - Perform action and wait 1 minute
I am encountering my problem only on the times when it is expected to wait. When this happens, the newTimer_Tick starts to tick every second. I have tried disabling the timer when it ticks and a few other things but none appeared to work. Below is the code:
public void Action(string result)
{
if (result.Contains("string1"))
{
// Check again in 10 + x seconds
int n = new Random().Next(0, 5000);
int newtime = 10000 + n;
newTimer.Tick += new EventHandler(newTimer_Tick);
newTimer.Interval = newtime;
newTimer.Enabled = true;
}
else if (result.Contains("string2"))
{
// Turn off
newTimer.Enabled = false;
}
else
{
// Perform action and tick again in 1min + x seconds
action1();
int n = new Random().Next(0, 5000);
int newtime = 600000 + n;
newTimer.Tick += new EventHandler(newTimer_Tick);
newTimer.Interval = newtime;
newTimer.Enabled = true;
}
}
private void newTimer_Tick(object sender, EventArgs e)
{
Action( result );
}
What have I done wrong?
Each time the following line is called, an new instance of the event handler newTimerTick is added to the invocation list for the Tick event:
newTimer.Tick += new System.EventHandler(newTimer_Tick);
So every time the time tick goes off newTimerTick is going to be called multiple times, which is going to give you unexpected results.
Configure your event handler once only. In the constructor would be a sensible place.
Have you tried to stop the timer with the Timer.Stop method?
Btw: I don't think you need to reassign the Tick event from the newTimer unless you don't create a new Timer everytime.
I think what you were missing is that you have to stop your timer since you don't actually want it to keep for more than one interval. You seem to want to run it once, check on the result and then decide if you want to keep running it or not. Here's the code:
public void action(string result)
{
int n = new Random().Next(0, 5000);
Boolean blActivateTimer = true;
Timer timer = new Timer();
timer.Tick += timer_Tick;
if (!result.Contains("string1") && !result.Contains("string2"))
{
n += 600000;
action1();
}
else
{
if (result.Contains("string1"))
{
n += 10000;
}
else
{
blActivateTimer = false;
}
}
if (blActivateTimer)
{
timer.Start();
}
}
void action1()
{
}
void timer_Tick(object sender, EventArgs e)
{
Timer t = (Timer)sender;
t.Stop();
action(result);
}