Application lags on each timer tick - c#

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.

Related

Two timers causing conflict in WinForms application

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.

System.Timers.Timer Elapsed event executing after timer.Stop() is called

Background: I have a timer that I am using to keep track of how long it has been since the serialPort DataReceived event has been fired. I am creating my own solution to this instead of using the built in timeout event because I am getting a continuous stream of data, instead of sending a query and getting one response.
The Problem:
In the DataReceived handler I have a statement to stop the timer so that is doesn't elapse. the problem is that a lot of the time it still executes the Elapsed handler afterword.
I have read that is is possible to use SynchronizingObject to solve this problem but I am not sure how to accomplish that.
Here is my code: I tried to cut out everything that I didn't think was relevant.
private System.Timers.Timer timeOut;
private System.Timers.Timer updateTimer;
public void start()
{
thread1 = new Thread(() => record());
thread1.Start();
}
public void requestStop()
{
this.stop = true;
this.WaitEventTest.Set();
}
private void record()
{
timeOut = new System.Timers.Timer(500); //** .5 Sec
updateTimer = new System.Timers.Timer(500); //** .5 Sec
timeOut.Elapsed += TimeOut_Elapsed;
updateTimer.Elapsed += updateTimer_Elapsed;
updateTimer.AutoReset = true;
comport.Open();
comport.DiscardInBuffer();
comport.Write(COMMAND_CONTINUOUSMODE + "\r");
stopwatch.Reset();
stopwatch.Start();
recordingStartTrigger(); //** Fire Recording Started Event
timeOut.Start();
updateTimer.Start();
this.waitHandleTest.WaitOne(); //** wait for test to end
timeOut.Stop();
updateTimer.Stop();
comport.Write(COMMAND_COMMANDMODE + Environment.NewLine);
comport.DiscardInBuffer();
comport.Close();
recordingStopTrigger(status); //** Fire Recording Stopped Event
stopwatch.Stop();
}
//***********************************************************************************
//** Events Handlers
private void comDataReceived_Handler(object sender, SerialDataReceivedEventArgs e)
{
double force = -100000;
string temp = "-100000";
//timeOut.SynchronizingObject.Invoke(new Action(()=> {timeOut.Stop();}), new object[] {sender, e});
timeOut.Stop();
//** I removed my action code here, keep things simple.
timeOut.Start();
}
private void TimeOut_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
timeOut.Stop();
updateTimer.Stop();
//** fire delegate that GUI will be listening to, to update graph.
if (eventComTimeOut != null && this.stop == false)
{
if (eventComTimeOut(this, new eventArgsComTimeOut(comport.PortName, "READ")))
{
//retry = true;
comport.Write(COMMAND_CONTINUOUSMODE + "\r");
updateTimer.Start();
timeOut.Start();
}
else
{
this.stop = true;
//retry = false;
this.WaitEventTest.Set();
status = eventArgsStopped.Status.failed;
}
}
}
void updateTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
//** fire delegate that GUI will be listening to, to update graph.
List<Reading> temp = new List<Reading>(report.Readings_Force);
eventNewData(this, new eventArgsNewData(temp));
}
This is well known behavior. System.Timers.Timer internally uses ThreadPool for execution. Runtime will queue the Timer in threadpool. It would have already queued before you have called Stop method. It will fire at the elapsed time.
To avoid this happening set Timer.AutoReset to false and start the timer back in the elapsed handler if you need one. Setting AutoReset false makes timer to fire only once, so in order to get timer fired on interval manually start timer again.
yourTimer.AutoReset = false;
private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
try
{
// add your logic here
}
finally
{
yourTimer.Enabled = true;// or yourTimer.Start();
}
}
I did a pause in timer with this code. for me that works.
Private cTimer As New System.Timers.Timer
Private Sub inittimer()
cTimer.AutoReset = True
cTimer.Interval = 1000
AddHandler cTimer.Elapsed, AddressOf cTimerTick
cTimer.Enabled = True
End Sub
Private Sub cTimerTick()
If cTimer.AutoReset = True Then
'do your code if not paused by autoreset false
End If
End Sub
Had the same problem and after some trying ended up with timer object to null and replace the timer variable it with a new timer object fixed the issue. I know its heavy of resources. But it solves the problem.

Manipulating UI elements from within another thread

I'm trying to have a seperate thread in a WinForms C# application start a background worker which controls a ProgressBar (marquee). The issue is that when i try to set the bar to visible it just does nothing, and i've tried many forms of Invoke but they don't seem to help.
The following method progressBarCycle is called from a separate thread.
BackgroundWorker backgroundWorker = new BackgroundWorker();
public void progressBarCycle(int duration)
{
backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted);
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.WorkerSupportsCancellation = true;
backgroundWorker.RunWorkerAsync(duration);
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
worker.ReportProgress(0);
DateTime end = DateTime.Now.AddMilliseconds((int)e.Argument);
while (DateTime.Now <= end)
{
System.Threading.Thread.Sleep(1000);
}
}
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (!this.IsHandleCreated)
this.CreateHandle();
statusStrip1.Invoke((MethodInvoker)delegate
{
progressBar1.Visible = false;
});
// if (!this.IsHandleCreated)
// {
// this.CreateHandle();
// if (InvokeRequired) this.Invoke((MethodInvoker)(() => progressBar1.Visible = false));
// else progressBar1.Visible = false;
// }
// else
// if (InvokeRequired) this.Invoke((MethodInvoker)(() => progressBar1.Visible = false));
// else progressBar1.Visible = false;
}
private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (!this.IsHandleCreated)
this.CreateHandle();
statusStrip1.Invoke((MethodInvoker)delegate
{
progressBar1.Visible = true;
});
// if (!this.IsHandleCreated)
// {
// this.CreateHandle();
// if (InvokeRequired) this.Invoke((MethodInvoker)(() => progressBar1.Visible = true));
// else progressBar1.Visible = true;
// }
// else
// if (InvokeRequired) this.Invoke((MethodInvoker)(() => progressBar1.Visible = true));
// else progressBar1.Visible = true;
}
Am I missing something obvious here? The comment sections are other things I've tried.
ProgressChanged is already raised on the UI thread (via the sync-context); your ProgressChanged does not need to do that Invoke - it can manipulate the UI directly (by contrast, DoWork can absolutely not do that). Perhaps the real problem is that you don't do any worker.ReportProgress(...) inside the loop - so it only happens once at the start.
Here's a full example:
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
using (var worker = new BackgroundWorker {
WorkerReportsProgress = true })
using (var progBar = new ProgressBar {
Visible = false, Step = 1, Maximum = 100,
Dock = DockStyle.Bottom })
using (var btn = new Button { Dock = DockStyle.Top, Text = "Start" })
using (var form = new Form { Controls = { btn, progBar } })
{
worker.ProgressChanged += (s,a) => {
progBar.Visible = true;
progBar.Value = a.ProgressPercentage;
};
worker.RunWorkerCompleted += delegate
{
progBar.Visible = false;
};
worker.DoWork += delegate
{
for (int i = 0; i < 100; i++)
{
worker.ReportProgress(i);
Thread.Sleep(100);
}
};
btn.Click += delegate
{
worker.RunWorkerAsync();
};
Application.Run(form);
}
}
}
Run progressBarCycle from the UI thread. RunWorkerAsync will
create the new thread for you.
In backgroundWorker_ProgressChanged simply call
progressBar1.Visible = true;. There is no need for Invoke.
Better also add a progressBar1.Refresh(); .
Another possibility to be aware of is that the progress bar is running on your UI thread. In order for the progress bar to be displayed and redraw itself to show new progress amounts, the UI thread must be running freely, processing windows messages in the main application loop.
So if you start your background worker thread but then your UI thread sits in a busy wait loop waiting for it to complete, or goes off and does loads of other work, then it won't be processing windows messages and your progress bar will be "unresponsive". You need to release the UI thread so that this updating still happens (i.e. return from your event handler and allow the UI to continue running as normal).
The danger of this is that if the UI is active, then the user can still interact with it. You therefore have to write the UI to be aware when the background worker is active, and handle the situation properly (problems can include: Allowing the user to start the background worker again while it is already running, UI trying to display information while the worker thread is busily updating it, the user deciding to load a new document or quit while the background worker is busy, etc). The two main solutions to this are to wrap every bit of UI in a protective shield that stops anything dangerous being initiated while the background work is running (this can be a lot of work if you have lots of controls to wrap in this way, and it's easy to make a mistake that lets a bug slip through) or to leave the UI "unprotected" but add an IMessageFilter that stops all "dangerous" user interaction (clicks and keypresses) by suppressing their incoming windows messages (WM_KEYPRESS etc) while the background processing is active.

backgroundworker vs Event

i am use my Winform application to send buffer of packet to my network card, my application show to progress via Progress Bar, if i choose to send all the buffer with out any delay between the packets my application stuck (become gray) and return to normal behaviour only after senging all the buffer (in case my buffer is very big, something like > 50,000 packets)
so how can i deal with such case ? maybee to change the backgroundworker who check all my class properties (number of packet send etc...) with event ?
listBoxFiles.SetSelected(0, true);
bgWoSingle = new BackgroundWorker();
bgWoSingle.WorkerReportsProgress = true;
bgWoSingle.ProgressChanged += new ProgressChangedEventHandler(bgW_ProgressChanged);
bgWoSingle.DoWork += new DoWorkEventHandler(
(s3, e3) =>
{
while (loopsCount < numberOfLoops && bContinuePlay && ifContinue)
{
for (int i = 0; (i < listBoxFiles.Items.Count) && bContinuePlay && ifContinue; i++)
{
this.Invoke((MethodInvoker)delegate
{
lbCurrentFileNum.Text = "(" + (i + 1) + "/" + listBoxFiles.Items.Count + "):";
});
string path = (string)listBoxFiles.Items[i];
pcap = new Pcap(path, playSpeed, isSync);
pcap._startTimer += new EventHandler(pcap_packetStartTimer);
pcap._stopTimer += new EventHandler(pcap__packetStopTimer);
pcap.evePacketProgress += new Pcap.dlgPacketProgress(
(progressCount) =>
{
pcap._fileSelectedIndex = i;
bgWoSingle.ReportProgress(progressCount, pcap);
});
if (selectedAdapter != null)
{
//play the file
bContinuePlay = pcap.playCapture(selectedAdapter._packetDevice); }
}
loopsCount++;
}
bgWoSingle.RunWorkerCompleted += new RunWorkerCompletedEventHandler(
(s3, e3) =>
{
}
);
bgWoSingle.RunWorkerAsync();
here i check the class properties (class name is pcap):
void bgW_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
//check the properties and update the UI
labelPacketSent.Text = pcap._numberOfSendPackets.ToString("#,##0");
progressBar1.Value = e.ProgressPercentage;
}
You have to use BackGroundWorker for it. I am adding a small example for it.
private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
backgroundWorker.ReportProgress(80, 15000);
}
private void BackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
var numberOfPackets = int.parse(e.UserState);
MessageBox.Show("Percentage: " + e.ProgressPercentage );
MessageBox.Show("Packets sent :" + numberOfPackets);
}
The worker can send the percentage of completion or any other data you want through UserState.
The idea is to have a PacketSender class which uses a BGWorker, and registers to it's Progress events, etc, and has a progress property or events of itself, to allow binding / listening.
Then, the PacketSender class will receive some big task to do, and will let it's BGWorker do it. Your BGWorker-Work method will be on a separate thread, and every time you call ReportProgress from within it, you will get it in your listening method in PacketSender in the main thread, and will be able to bubble it upwards to PacketSender.ProgressChanged listeners or change a ProgressChanged property of percents complete, etc.
The right way to do it would be getting the data for the task in the PacketSender ctor, and immediately start the BGWorker, to prevent having multiple tasks on the same PacketSender, or have a Start and Stop method which will check if the BGWorker is working, and will prevent multi-tasking a single PacketSender.
Its better to use BackgroundWorker here. It's new and better than defining custom events to update your progress bar. Check MSDN.

BackgroundWorker slow

i am developing a project (WPF) and i have a Datagrid the load more than 5000 records from the database so i used a BackgroundWorker to advice the user the data is loading but it is so slow , i need to wait almost 2 minutes to load the data from the database,instead if i don't use the BackgroundWorker i need to wait just 3 second to load the data in the Datagrid.
Here i write down the code snippet that i use for the BackgroundWorker :
private void RunWorker()
{
worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.WorkerSupportsCancellation = true;
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
worker.RunWorkerAsync();
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker senderWorker = sender as BackgroundWorker;
dc = new DataClasses1DataContext();
var query = from c in dc.Contact_DDBB_Xavis
select
new
{
c.ContactID,
c.Continent,
c.Country,
c.City,
c.PostalCode,
c.CompanyName,
c.UserCreated,
c.DateCreated,
c.UserModified,
c.DateModified
};
if (query.Count() > 0)
{
for (int i = 0; i < query.Count(); i++)
{
int progressInPercent = (int)(((decimal)(i + 1) / (decimal)query.Count()) * 100);
worker.ReportProgress(progressInPercent, i);
System.Threading.Thread.Sleep(10);
e.Result = query.ToList();
}
}
if (senderWorker.CancellationPending)
{
e.Cancel = true;
}
}
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error == null)
{
this.dataGrid.DataContext = e.Result;
backGround.Visibility = Visibility.Collapsed;
duracel.Visibility = Visibility.Collapsed;
txtBackWORK.Visibility = Visibility.Collapsed;
}
}
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
for (double i = 0.0; i < e.ProgressPercentage; i++)
{
duracel.pthFiller.Width = 0;
double max = 312;
max = (double)e.ProgressPercentage;
duracel.pthFiller.Width = e.ProgressPercentage * 3.12;
duracel.txtStatus.Text = e.ProgressPercentage + " %";
txtBackWORK.Text = String.Format("Loading " + e.ProgressPercentage + " %");
}
}
now i don't know if there is something wrong in my code so i ask you some advice to how load faster the data from database without wait so long time.
Thanks for your attention.
Have a good time.
Cheers
Each time you call query.Count(), you're running another SQL query.
You should call Count() once, and store it in a local variable.
Also, you should only call `ReportProgress if the progress actually changed. (There's no point in calling it 1,000 times)
Umm... you are calling System.Threading.Thread.Sleep() in your code, in a loop, which appears to be calling the query multiple times.
Your update code also seems to be needlessly looping from 0 to the current percentage... not sure why?
Get rid of the whole progress indicator block as it exists. You are iterating over every single record, pausing your thread, re-calling the query and assigning it to e.Result and generally incurring overhead 5,000 times after your data has already loaded. This is useless.
If the data loads in a matter of seconds, show a marquee progressbar on the UI thread and everyone will be happy.

Categories