I am wondering what would be the best solution to a practical problem.
I am not using any threads in this small project.
It is a simple UI polling data from the serial port on a fixed timer.
Data is analyzed, filtered, and then displayed on a line chart.
Everything is working fine other than data polling "hanging" (i.e. not being executed, no error or anything) when the form is moved around on the desktop.
I don't necessarily need the chart to be updated when the form is being moved but I would at least want the timer to still tick while it's being moved (so that data polling continues).
My timer is declared as follow:
private System.Windows.Forms.Timer timer1;
I don't have any threads defined myself, but my understanding is that timer ticks are taking place on a separate thread. Is that right?
I am handling the timer tick event like this.
private void timer1_Tick(object sender, EventArgs e)
{
if (chkBoxPosition.Checked)
{
tBoxPosition.Text = ExecuteCommand("r 1\n", tBoxPosition.Text, false, false);
Axis.position = TryParseDouble(Axis.position, tBoxPosition.Text);
}
}
I have more stuff in this event (a chart).
It's working decently, however when I drag the UI winform on the desktop it "freezes" the controls temporarily until I let go of the UI. It doesn't crash or anything, it just doesn't refresh as I am moving the window.
Not a huge deal as far as the controls are concerned, however, I just realized that the entire thread or timer were also hanging, as for the entire time where I am holding the mouse button dragging the window around nothing seems to be taking place.
The question does not specify what chart package is being used, so i'm guessing the built in one from .Net.
The background worker is a good option, but it's pretty old school and takes more implementation than using async Task.
Note: The only time it's OK to use async void is on top level event handlers, otherwise you should use async Task, see this
I made it so the checkbox enables and disabled the timer, not sure what it does in your app.
Here are 2 versions of your code, v1 is where the text box gets updated after the ExecuteCommand has finished and v2 is where the text box gets updated within the ExecuteCommnad method.
Version 1: (update text on timer tick)
public partial class Form1 : Form
{
Series Series1 { get; set; }
int val = 0;
public Form1()
{
InitializeComponent();
timer1.Interval = 2000;
chart1.Series.Clear();
Series1 = new Series
{
Name = "Series1",
Color = System.Drawing.Color.Green,
IsVisibleInLegend = false,
IsXValueIndexed = true,
ChartType = SeriesChartType.Line
};
this.chart1.Series.Add(Series1);
}
private async void timer1_Tick(object sender, EventArgs e)
{
tBoxPosition.Text = await ExecuteCommand("r 1\n", tBoxPosition.Text, false, false);
tBoxPosition.Select(tBoxPosition.Text.Length - 1, 0);
tBoxPosition.ScrollToCaret();
}
//not sure what you are doing her but lets say its something that takes some time..maybe a data fetch of some kind
async Task<string> ExecuteCommand(string str, string text, bool value1, bool value2)
{
StringBuilder returnString = new StringBuilder(text);
for (int i = 0; i < 10; i++)
{
await Task.Delay(100);
returnString.AppendLine($"value : {val++}");
Series1.Points.AddXY(i, (i + i + i));
}
return returnString.ToString();
}
private void chkBoxPosition_CheckedChanged(object sender, EventArgs e)
{
if (chkBoxPosition.Checked)
timer1.Enabled = true;
else
timer1.Enabled = false;
}
}
version 2: (update text box and chart in execute method)
private async void timer1_Tick(object sender, EventArgs e)
{
await ExecuteCommand("r 1\n", tBoxPosition.Text, false, false);
}
//not sure what you are doing her but lets say its something that takes some time..maybe a data fetch of some kind
async Task ExecuteCommand(string str, string text, bool value1, bool value2)
{
for (int i = 0; i < 10; i++)
{
await Task.Delay(100);
Series1.Points.AddXY(i, (i + i + i));
tBoxPosition.Text += $"value : {val++} {Environment.NewLine}";
tBoxPosition.Select(tBoxPosition.Text.Length - 1, 0);
tBoxPosition.ScrollToCaret();
}
}
Related
I developed a simple windows form application showing a realtime updated chart:
In the following code:
private void Start_Click(object sender, EventArgs e)
{
cpuThread = new Thread(new ThreadStart(this.ThreadUpdateChart));
cpuThread.IsBackground = true;
cpuThread.Start();
}
private void ThreadUpdateChart()
{
if (chart1.IsHandleCreated)
{
this.Invoke((MethodInvoker) delegate { UpdateChart();});
}
else
{
//......
}
Thread.Sleep(1000);
}
private void UpdateChart()
{
for (int i = 0; i < sampleSize; i++)
{
chart1.Series["Signal"].Points.AddXY(i, 100 * var.NextDouble());
chart1.Update();
//Thread.Sleep(500);
}
}
My problem is that when I press the start button the chart is update correctly, but this freeze my GUI till the end of the updated, I would like to update the chart without freezing other GUI functions, is it possible? Any solution?
To start of, the threading does nothing useful in your example, it just runs some code on a background thread, that immediately asks the main thread to update the chart. So get rid of it unless you are doing something computationally expensive not shown in the example.
You should also get better performance by doing all changes to your chart at once, avoiding expensive things like rendering until all changes have been made. So I would try something like this:
private void UpdateChart()
{
var series = chart1.Series["Signal"];
for (int i = 0; i < sampleSize; i++)
{
series.Points.AddXY(i, 100 * var.NextDouble());
}
chart1.Update();
}
If this does not help I would recommend doing some profiling to find out what is actually blocking the UI thread.
I have a button that performs an action that takes 3 seconds (reads data from an API and refreshes the screen). While the action is performed, I would like the button to rotate.
My problem is that it starts to rotate AFTER the 3seconds operation has been completed...
and, ofc it stops immediately, as it hits ClearAnimation..
Here's my code
this.refreshButton.Click += this.RefreshPendingOrders_Click;
private void RefreshPendingOrders_Click(object sender, EventArgs e)
{
this.StartRotateAnimation();
System.Threading.Thread.Sleep(3000);
this.refreshButton.ClearAnimation();
}
private void StartRotateAnimation()
{
var pivotX = this.refreshButton.Width / 2;
var pivotY = this.refreshButton.Height / 2;
var animation = new RotateAnimation(0f, 360f, pivotX, pivotY)
{
Duration = 500,
RepeatCount = 3
};
this.refreshButton.StartAnimation(animation);
}
The issue is that you are calling Thread.Sleep() which in case of a Click EventHandler will be happening on the UI thread. This means you are blocking the UI Thread, which is responsible for running your animations for 3 seconds.
You could change your code to use async/await instead like:
private async void RefreshPendingOrders_Click(object sender, EventArgs e)
{
this.StartRotateAnimation();
await Task.Delay(3000);
this.refreshButton.ClearAnimation();
}
Im building a little app which has a long loading time.
I want to display this loading time in a progressbar to see how long i have to wait till the programm is loaded.
I hope you understand what i want..
I tried the backgroundworker already but dont understand how to use it, in every example they use in the DoWork Event a simple
for (int i = 0; i < 100; i++)
{
//method etc here
backgroundWorker.ReportProgress(i);
}
But in my eyes this is senseless for me because this only repeats my method...
Thank you in advance!
EDIT:
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
Aktie dataAktie = new Aktie(aktien_name);
for (int i = 0; i < 100; i++)
{
dataAktie.ReadFromDatabase();
dataAktie.FetchData();
backgroundWorker.ReportProgress(i);
}
}
private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//Controls that have to be filled
}
But this Controls dont get data im veryyyyy confused
The following code example demonstrates the use of the ReportProgress method to report the progress of an asynchronous operation to the user.
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// This method will run on a thread other than the UI thread.
// Be sure not to manipulate any Windows Forms controls created
// on the UI thread from this method.
backgroundWorker.ReportProgress(0, "Working...");
Decimal lastlast = 0;
Decimal last = 1;
Decimal current;
if (requestedCount >= 1)
{ AppendNumber(0); }
if (requestedCount >= 2)
{ AppendNumber(1); }
for (int i = 2; i < requestedCount; ++i)
{
// Calculate the number.
checked { current = lastlast + last; }
// Introduce some delay to simulate a more complicated calculation.
System.Threading.Thread.Sleep(100);
AppendNumber(current);
backgroundWorker.ReportProgress((100 * i) / requestedCount, "Working...");
// Get ready for the next iteration.
lastlast = last;
last = current;
}
backgroundWorker.ReportProgress(100, "Complete!");
}
** http://msdn.microsoft.com/en-us/library/a3zbdb1t%28v=vs.110%29.aspx
A BackgroundWorker and it's ReportProgress method are no magic wanda that simply shows you any progress you want, you actually have to change your code to do so.
The DoWork event handler should contain the code you want to execute in the background. Ideally this is something for progress can be measured easily. For example if you have to process 10 items then after each item you could say I'm now 10% further done. That's why the example code contains a for loop.
Your code only contains two method calls, ReadFromDatabase and FetchData. So you could simply do
dataAktie.ReadFromDatabase();
backgroundWorker.ReportProgress(50); // 50% done
dataAktie.FetchData();
backgroundWorker.ReportProgress(100); // 100% done
Obviously that not really perfect. The only way to have more accurate progress is to change the ReadFromDatabase and FetchData methods, for example let them take the BackgroundWorker object as a parameter so that they can also report progress, or provide a callback for that.
Why does my first attempt to change a button's text in this code not work, while the third attempt does work ?
My user will have to wait a few seconds while the serial port connects. After that, I want to alert him that he has already connected (a second attempt can cause problems).
I wanted to let him know that things are okay, so he won't think "duh" and click twice.
Fail. The text change never appears.
Okay, why does the third change in button text work, but the first one does not ? I don't know if the second one works or not.
/***********************************************************************
* Button: Connect Port *
***********************************************************************/
private void TheConnectPortButton_Click(object sender, EventArgs e)
{
string OldText = TheConnectPortButton.Text;
TheConnectPortButton.Text = "Busy, Please Wait"; /////// This never happens
ButtonBoss.ButtonHandler_ConnectPort();
TheConnectPortButton.Text = OldText; /////// Not sure if this happens
if (aUartSemaphoreThatTells.IfWeHaveConnectedToAPort == (int)aValueWhichIndicatesThat.YesWeHaveAGoodPortConnected)
{
TheConnectPortButton.Text = "Connected"; /////// This one does happen
}
}
the aUartSemaphoreThatTells.IfWeHaveConnectedToAPort is also used by the ButtonBoss routine to make sure he doesn't connect a second time, along with other button routines (e.g., make sure we are connected before we Tx/Rx or whatever).
I tried changing the code after the routine returns to look like this...
if (aUartSemaphoreThatTells.IfWeHaveConnectedToAPort == (int)aValueWhichIndicatesThat.YesWeHaveAGoodPortConnected)
{
TheConnectPortButton.Text = "Connected";
}
else
{
TheConnectPortButton.Text = OldText;
}
...and I still get the same result.
My guess (and that's all it is) is that threading is somehow involved in all this, and that the serial port routines trump the button text changing routines by some convolution that I don't follow properly at the moment.
Question: What do I need to do to get the text to change before the connection stuff hogs the system ?
(If that's what's happening)
Question 2: If I can't make this happen, I think I've read about "greying out" the buttons, or, I believe I saw somewhere that I can actually make a button go away right before the user's eyes so that he can't click it again. Links to example code would be welcome.
The problem is you're doing everything from one and the same event-handler consequently, so that the button has no time to get updated (redrawn). You could call Application.DoEvents(); method, but it's not a good idea at all, please, read Use of Application.DoEvents()
I think usually you're expected to push a time-consuming task into a separate thread, get progress report from it and update your GUI. There is a plenty of ways to create a "worker" thread and get some respond from it. For example, use a BackgroundWorker Class:
public partial class Form1 : Form
{
public Form1() { InitializeComponent(); }
private void button1_Click(object sender, EventArgs e)
{
BackgroundWorker w = new BackgroundWorker();
w.WorkerReportsProgress = true;
w.DoWork += new DoWorkEventHandler(w_DoWork);
w.ProgressChanged += new ProgressChangedEventHandler(w_ProgressChanged);
w.RunWorkerCompleted += new RunWorkerCompletedEventHandler(w_RunWorkerCompleted);
w.RunWorkerAsync();
button1.Text = "Started";
}
//may influence GUI, as this event handler is run on the GUI thread
void w_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
button1.Text = "Job is done";
}
//may influence GUI, as this event handler is run on the GUI thread
void w_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
button1.Text = e.ProgressPercentage.ToString();
}
//runs in the worker thread...should do the actual job
//may influence GUI through `ReportProgress`
//or through `Invoke` method
void w_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 1; i <= 10; i++)
{
Thread.Sleep(500);
worker.ReportProgress(10 * i);
}
}
}
Or you may use Task Class:
public partial class Form1 : Form
{
public Form1() { InitializeComponent(); }
private void button1_Click(object sender, EventArgs e)
{
new Task(DoTask).Start();
}
void DoTask()
{
for (int i = 1; i <= 10; i++)
{
Thread.Sleep(500);
//here you need to update GUI through `Invoke` method
//as the GUI may only be influenced from the the thread,
//where it's created
this.Invoke(new Action<int>((j) =>
{
button1.Text = j.ToString();
}), 10 * i);
}
}
}
Background:
I am using a simple progress dialog in an Outlook Add-in to show progress while performing long operations. Since I cannot run code that uses Outlook objects in a separate thread, I cannot implement a more traditional background worker process. My add-in has been working OK until Outlook 2013 where in certain instances my progress dialog hangs. When I run the add-in in the VS debugger and cause the hang, then do a break, it appears to be stuck on the DoEvents() line that tries to force the progressbar to update.
My Question:
Can someone suggest a better system to show progress with the restriction above (long running code must run in main Outlook thread). Is there a better way to make the progress dialog responsive without using DoEvents()?
The following simple code demonstrates how I am doing this now. In the add-in code that is performing long operations on Outlook objects:
private void longRunningProcess()
{
int max = 100;
DlgStatus dlgstatus = new DlgStatus();
dlgstatus.ProgressMax = max;
dlgstatus.Show();
for (int i = 0; i < max; i++)
{
//Execute long running code that MUST best run in the main (Outlook's) thread of execution...
System.Threading.Thread.Sleep(1000); //for simulation purposes
if (dlgstatus.Cancelled) break;
dlgstatus.SetProgress("Processing item: " + i.ToString(), i);
}
}
Here's the code for the simple progress dialog window:
public partial class DlgStatus : Form
{
private bool _cancelled;
public DlgStatus()
{
InitializeComponent();
}
public int ProgressMax
{
set
{
progress.Maximum = value;
Application.DoEvents();
}
}
public bool Cancelled
{
get { return _cancelled; }
}
public void SetProgress(string status, int val)
{
lblStatus.Text = status;
progress.Value = val;
Application.DoEvents(); //Seems to hang here
}
private void btnCancel_Click(object sender, EventArgs e)
{
_cancelled = true;
Application.DoEvents();
this.Visible = false;
}
}
I was able to accomplish this by doing the following. The Custom form has a progressbar with the style set to Marquee.
I got the general method from http://social.msdn.microsoft.com/Forums/en/vsto/thread/59993421-cbb5-4b7b-b6ff-8a28f74a1fe5 but found that I did not need to use all the custom window handles.
private void btn_syncContacts_Click(object sender, RibbonControlEventArgs e)
{
Thread t = new Thread(SplashScreenProc);
t.Start();
//long running code
this.SyncContacts();
syncingSplash.Invoke(new Action(this.syncingSplash.Close), null);
}
private SyncingContactsForm syncingSplash = new SyncingContactsForm();
internal void SplashScreenProc(object param)
{
this.syncingSplash.ShowDialog();
}
It is important to note that the form does not work with the Outlook object model. It is not recommended by Microsoft to use the object model on separate threads.