How to perform an async UI update in C# WPF? [duplicate] - c#

This question already has answers here:
How to put delay before doing an operation in WPF
(2 answers)
Closed last month.
I have a simple C# .NET WPF app that should display all pictures of a folder, each for half a second using an image element. PollPic is a property (of variable pollPic). currImageFilename was declared above. I aimed to use Invoke/BeginInvoke in order to update the UI. The function where this code belongs to is a async function, that is called (with await) from a button click event.
When I have 6 pictured in the folder, each has been read and and sleep was called 6 times but only the last picture was displayed in the end. Where is my general thinking mistake here?
Thanks everybody.
if (picPath != "")
{
string[] pollPicList = Directory.GetFiles(picPath);
if (pollPicList.Length > 0)
{
for (int i = 0; i < pollPicList.Length; i++)
{
currImageFilename = pollPicList[i];
PollPic = new BitmapImage(new Uri(currImageFilename));
this.Dispatcher.BeginInvoke(new Action(() => ShowDetectPic(PollPic)));
System.Threading.Thread.Sleep(500);
}
}
}
Unsuccessfully tried to use Task.Run instead.
Also not working Task t1 = new Task(() => ShowDetectPic(PollPic));

You can't sleep the GUI thread or nothing will render. Your async code needs to be the waiting, not whatever else it is you're doing there.
async ... Function()
{
string[] pollPicList = Directory.GetFiles(picPath);
if (pollPicList.Length > 0)
{
for (int i = 0; i < pollPicList.Length; i++)
{
currImageFilename = pollPicList[i];
PollPic = new BitmapImage(new Uri(currImageFilename));
ShowDetectPic(PollPic);
await Task.Delay(500);
}
}
}

Related

How to update a UI element multiple times while in an event handler?

I have an event handler on a button that runs some lines of code. All the code does is run bubble sort on a list but also does some changes to some rectangles on a canvas. My problem is those changes only appear once it has exited the event handler. A similar question has been asked before however that only does one change instead of multiple in concession UI update in WPF elements event handlers.
This is a similar goal to what I'm trying to achieve.
If you're interested in what exactly the code is:
private void Run_btt_Click(object sender, RoutedEventArgs e)
{
Key hold = new Key();
var converter = new System.Windows.Media.BrushConverter();
double hold2, hold3;
//for inside a for loop(bubble sort)
for (int i = 0; i < Sequence.Count; i++)
{
for (int c = 0; c < Sequence.Count-i-1; c++)
{
//changes the colour of the 2 that are being compared
Sequence[c].shape.Fill =(Brush)converter.ConvertFromString("#FFFF00");
Sequence[c+1].shape.Fill = (Brush)converter.ConvertFromString("#FFFF00");
//pause for a bit so that you can see what the algorithm is doing
Thread.Sleep(delay);
if (Sequence[c].Value > Sequence[c + 1].Value)
{
// swap the 2 rectangles
hold2 = Canvas.GetLeft(Sequence[c].shape);
hold3 = Canvas.GetLeft(Sequence[c + 1].shape);
hold = Sequence[c];
Sequence[c] = Sequence[c + 1];
Sequence[c + 1] = hold;
Canvas.SetLeft(Sequence[c].shape, hold2);
Canvas.SetLeft(Sequence[c + 1].shape, hold3);
}
//set colour back to normal
Sequence[c].shape.Fill = (Brush)converter.ConvertFromString(Sequence[c].Colour);
Sequence[c + 1].shape.Fill = (Brush)converter.ConvertFromString(Sequence[c + 1].Colour);
}
}
}
I'm sorry if this question is too vague or not enough detail but I don't know much about this topic and really just starting out.
Just putting #Clemens comment as answer.
If you give the method the async like:
public void async methodname()
and then once you need to update the UI you add:
await Task.Delay(1);
This will act like break point in your code that will update the UI with the changes you made. The 1 represents how long in millisecond the delay will be.

Prevent hanging in windows forms

I'm having a problem.
So I've built an app which displays data in the form of chart and a datagridview. They are both responsive. That means they rescale and move with the data. It takes some computation power I guess.
At the same time I have timers cause it all runs periodically with f=4Hz.
And now: When I run the app and switch on the periodical readout the app hangs during resizing. How could I prevent it?
I've already tried to use a backgroundworker, but the problem occurs in the moment of accessing to the datagridview and chart which are declared (and also used) in the "other thread" (as the VS said)
So.. How could I prevent it?
Maybe I should utilise the backgroundworker in the other way?
My attempts with the backgroundworker:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
//Control.CheckForIllegalCrossThreadCalls = false;
if (!GetConnectionStatus())
{
stop_ticking();
if (MessageBox.Show("Device not connected", "Connection status", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error) == DialogResult.Retry)
messaging();
else
return;
}
// TEMP READ
Read_temp(tlist);
float[] t = new float[3];
float[] r = new float[3];
float[] av = new float[1];
float[] st = new float[1];
// TEMP IMPORT
tlist.Give_current_temp(t, r, av, st);
string time_stamp = tlist.Give_current_time();
rows_nr++;
// ADDING TO GRID
dataGridView1.Invoke(new Action(() => { dataGridView1.Rows.Add(new object[] { rows_nr, time_stamp, av[0], st[0], (t[0]).ToString(), (r[0]).ToString(), (t[1]).ToString(), (r[1]).ToString(), (t[2]).ToString(), (r[2]).ToString() }); }));
//dataGridView1.Rows.Add(new object[] { rows_nr, time_stamp, av[0], st[0], (t[0]).ToString(), (r[0]).ToString(), (t[1]).ToString(), (r[1]).ToString(), (t[2]).ToString(), (r[2]).ToString() });
dataGridView1.Invoke(new Action(() => { dataGridView1.FirstDisplayedScrollingRowIndex = dataGridView1.RowCount - 1; }));
//dataGridView1.FirstDisplayedScrollingRowIndex = dataGridView1.RowCount - 1;
// ADDING TO CHART
for (int i = 0; i < 3; i++)
chart1.Invoke(new Action(() => { chart1.Series[series_names[i]].Points.AddXY((rows_nr), (t[i])); }));
//chart1.Series[series_names[i]].Points.AddXY((rows_nr), (t[i]));
chart1.Invoke(new Action(() => { chart1.Series["average"].Points.AddXY((rows_nr), (av[0])); }));
//chart1.Series["average"].Points.AddXY((rows_nr), (av[0]));
//chart1.Series["std1"].Points.AddXY((rows_nr), (av[0] + Math.Abs(st[0])));
//chart1.Series["std2"].Points.AddXY((rows_nr), (av[0] - Math.Abs(st[0])));
// MOVING CHART
if (chart1.Series[series_names[0]].Points.Count > nr_of_noints_graph)
{
for (int i = 0; i < 3; i++)
chart1.Series[series_names[i]].Points.RemoveAt(0);
chart1.Series["average"].Points.RemoveAt(0);
//chart1.Series["std1"].Points.RemoveAt(0);
//chart1.Series["std2"].Points.RemoveAt(0);
chart1.ChartAreas[0].AxisX.Minimum = rows_nr - (nr_of_noints_graph - 1);
chart1.ChartAreas[0].AxisX.Maximum = rows_nr;
dataGridView1.Rows.RemoveAt(0);
}
chart1.Invoke(new Action(() => { chart1.ChartAreas[0].RecalculateAxesScale(); }));
//chart1.ChartAreas[0].RecalculateAxesScale();
}
Please take a look at background worker sample. You are doing it wrong. Background worker DoWork should not call UI controls and is executed in non UI thread, it should execute time consuming computing and call worker.ReportProgress(). While ReportProgress method can access UI controls and code in this method is executed in UI thread.
Some chart controls are lugging when adding/removing points. Maybe it hangs because it lugs. Make updates less frequently (1 in 1 second for example) and see whether it hangs or not.
https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.backgroundworker?view=netframework-4.7.2
Wrap operations in Stopwatch and use System.Diagnostics.Debug.WriteLine to trace execution flow and time spent on the operations.
Moving chart part does not work because it accesses UI elements in non ui thread without Invoke to UI thread.
If it was not Background worker I would write it this way:
// MOVING CHART
chart1.Invoke(new Action(()=>
{
if (chart1.Series[series_names[0]].Points.Count > nr_of_noints_graph)
{
for (int i = 0; i < 3; i++)
chart1.Series[series_names[i]].Points.RemoveAt(0);
chart1.Series["average"].Points.RemoveAt(0);
chart1.ChartAreas[0].AxisX.Minimum = rows_nr - (nr_of_noints_graph - 1);
chart1.ChartAreas[0].AxisX.Maximum = rows_nr;
}
}
));
I wouldn't wrap each operation in separate Invokes as well.
As for your question it's insufficient information to detect what is wrong please provide minimum viable runnable sample which demonstrates the problem.
As #Access Denied states you should improve separation between GUI and Background worker threads. You could execute // TEMP READ and // TEMP IMPORT operations on background thread and make a call to the GUI thread via .Invoke method when all the data is ready. Read "How to: Make Thread-Safe Calls to Windows Forms Controls" article for more information.
When you add/update data in your DataGridView use .BeginUpdate/.EndUpdate methods to prevent control update until all the data is refreshed.
Other approach is to use Virtual mode. It's especially usefull if you have many items in grid.
When working with a background thread you must not create, update, or even access any UI element.
You need to separate the work that retrieves your data (the slow part) from the work that updates the chart (which is very fast).
It really comes down to doing it like this:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
if (!GetConnectionStatus())
{
stop_ticking();
return;
}
// TEMP READ
Read_temp(tlist);
float[] t = new float[3];
float[] r = new float[3];
float[] av = new float[1];
float[] st = new float[1];
// TEMP IMPORT
tlist.Give_current_temp(t, r, av, st);
string time_stamp = tlist.Give_current_time();
rows_nr++;
chart1.Invoke(new Action(() =>
{
// ADDING TO GRID
dataGridView1.Rows.Add(new object[] { rows_nr, time_stamp, av[0], st[0], (t[0]).ToString(), (r[0]).ToString(), (t[1]).ToString(), (r[1]).ToString(), (t[2]).ToString(), (r[2]).ToString() });
dataGridView1.FirstDisplayedScrollingRowIndex = dataGridView1.RowCount - 1;
// ADDING TO CHART
for (int i = 0; i < 3; i++)
{
chart1.Series[series_names[i]].Points.AddXY((rows_nr), (t[i]));
}
chart1.Series["average"].Points.AddXY((rows_nr), (av[0]));
// MOVING CHART
if (chart1.Series[series_names[0]].Points.Count > nr_of_noints_graph)
{
for (int i = 0; i < 3; i++)
{
chart1.Series[series_names[i]].Points.RemoveAt(0);
}
chart1.Series["average"].Points.RemoveAt(0);
chart1.ChartAreas[0].AxisX.Minimum = rows_nr - (nr_of_noints_graph - 1);
chart1.ChartAreas[0].AxisX.Maximum = rows_nr;
dataGridView1.Rows.RemoveAt(0);
}
chart1.ChartAreas[0].RecalculateAxesScale();
}));
}
If you have to show a MessageBox then you also need to invoke that.

Fire and forget method in C# [duplicate]

This question already has answers here:
Captured variable in a loop in C#
(10 answers)
Closed 6 years ago.
Fire and forget method in C#
I refered different issues for 'Fire and Forget in C#'
i.e.
Simplest way to do a fire and forget method in C#?
.
.
and few several,
but i have an another issue with the same.i wrote following code
static void Main(string[] args)
{
for (int i = 0; i < 5; i++)
{
//fireAway(i);
Task.Factory.StartNew(() => fireAway(i));
}
Console.WriteLine("Main Finished");
Console.Read();
}
public static void fireAway(int i)
{
System.Threading.Thread.Sleep(5000);
Console.WriteLine("FireAway" + i);
}
where i am expecting output like
Main Finished
FireAway0
FireAway1
FireAway2
FireAway3
FireAway4
but output is
Main Finished
FireAway5
FireAway5
FireAway5
FireAway5
FireAway5
very honestly i am new to threading concept, i need help. How can i meet with expected output..?
The threads are started after the loop is finished. When the loop is finished the value of i is 5. You have to capture the value of i before you send it to StartNew(..)
for (int i = 0; i < 5; i++)
{
int tmp = i;
Task.Factory.StartNew(() => fireAway(tmp));
}
You should pass parameter to your method, not use i, because the method will start to execute only after you finish to iterate, see here

picbox.BorderStyle = BorderStyle.FixedSingle | Cross-thread operation not valid

I'm working on a C# Winforms project which loads images in a grid, I implemented parallelism and thread to learn a bit about it and there's something I can't figure out. I need to change the BorderStyle of a set of pictureboxes in run time, here's my code:
Task.Factory.StartNew(() =>
{
Parallel.For(0, img.Count, i =>
{
Bitmap tmp_b = new Bitmap((System.Drawing.Image)img[i].RenderImage(0));
imagenes[i] = tmp_b;
Progress_Bar_Loading_Images.Invoke((Action)delegate { reportarprogreso(); });
});
for (int i = inicio_set; i < final_set; ++i)
{
picbox[i].Image = imagenes[i];
picbox[i].BorderStyle = BorderStyle.FixedSingle;
}
});
The problem is in this line: picbox[i].BorderStyle = BorderStyle.FixedSingle; I get an error message saying: Control '' accessed from a thread other than the thread it was created on.
I think I know why this is happening but I'm not quite sure how to solve it. I tried putting the for (int i = inicio_set; i < final_set; ++i) outside the Task but the images won't get assigned to the pictureboxes since it's a different thread (main) and the images are not available until the task completes so I think I need to update the BorderStyle the same way I update the ProgressBar but not sure how that would be.
Thank you,
Matias.
You execute the for loop in a different thread.
Change the for body to:
if (picbox[i].InvokeRequired)
{
picbox[i].Invoke(new Action(() =>
{
picbox[i].BorderStyle = BorderStyle.FixedSingle;
}));
}
else
{
picbox[i].BorderStyle = BorderStyle.FixedSingle;
}
or simply use Async - Await on the task; make the caller method Async and the task:
await Task.Factory.StartNew(...)

prevent freeze gif file at the same time searching text in database [duplicate]

This question already has answers here:
Prevent a WinForms PictureBox animated GIF from pausing during processing?
(2 answers)
Closed 8 years ago.
I want search data in database and show result. When I searching word in db, I want show a group box(GbLoading) with label and gif image control, But gif file freeze when searching word. How can I change my code for prevent freezing gif image in group box of form.
my code is :
private void BtSearch_Click(object sender, EventArgs e)
{
GbLoading.Location = new Point(this.Width / 2 - 100, this.Height / 2 - 30);
GbLoading.Visible = true;
Application.DoEvents();
List<BookAuthorsFieldSet> Resources = Db.Search(ResourceOrAutor, TxSearch.Text.Trim());
DataTable Dt = ConvertListToDataTable(Resources);
DgvResourcesOrAuthors.DataSource = Dt;
DgvResourcesOrAuthorsCount.Text = "Result Count : " + DgvResourcesOrAuthors.Rows.Count.ToString();
Application.DoEvents();
DgvSpecialResourcesOrAuthors.DataSource = null;
GbLoading.Visible = false;
}
Do an asynctask for the db search/load. I'd also suggest your activity Implements an interface that you create, perhaps ILoadCompleted with one method loadcompleted or something like that. Then you send a reference to your activity and on postexecute you use your reference ILoadCompleted to execute the loadcompleted so your activity knows that the asynctask is done.
If your DB class support async methods then use them. If not, then you can create a Task and await it
List<BookAuthorsFieldSet> Resources = await Task.Run(
() => Db.Search(ResourceOrAutor, TxSearch.Text.Trim()));
PS: Don't forget to mark your method as async
For .Net 4.0
Task.Factory.StartNew(
() => Db.Search(ResourceOrAutor, TxSearch.Text.Trim()) )
.ContinueWith(t =>
{
//Move the rest of your code here....
List<BookAuthorsFieldSet> Resources = t.Result;
//......
},TaskScheduler.FromCurrentSynchronizationContext());

Categories