Threading issue - UI updates before the webservice has finished - c#

I've searched for examples on how to fix
the problem below, but can't seem to find what I need and it's starting to
drive me potty!
Essentially, I have a race condition whereby the webservice is finishing way
after return has been given to the UI, so the UI is not displaying anything
at all.
Here's the code. The webservice does actually get the correct data which is
the pain of it all! The generateNewScreen method comes as a result of
clicking some text on a ListView area.
Calling routine
private void generateNewScreen(int t)
{
string[] races = new string[] { };
View currentview = FindViewById<View>(Resource.Id.relLayout);
TextView text = FindViewById<TextView>(Resource.Id.textTitle);
ListView listView = FindViewById<ListView>(Resource.Id.listView);
ImageView image = FindViewById<ImageView>(Resource.Id.imgBack);
image.Visibility = ViewStates.Visible;
Console.WriteLine("t = {0}, addFactor = {1}", t,addFactor);
switch (addFactor)
{
case 0:
switch (t)
{
case 0: races =listviewInfo(Resource.Array.RaceTracks,
Resource.Drawable.Back_RaceHorsePlace, Resource.String.Tracks);
addFactor = 10;
break;
case 1: List<string>
race = new List<string>();
currentview.SetBackgroundDrawable(Resources.GetDrawable(Resource.Drawable.Back_BobMoore));
text.Text = Resources.GetString(Resource.String.ComingSoon);
webservice_user getRace = new webservice_user();
race = getRace.getUpcomingRaces("RP");
races = race.ToArray();
addFactor = 20;
break;
}
if (t < 6 || t == 7)
listView.Adapter = new ArrayAdapter<string>(this, Resource.Layout.listview_layout, races);
break;
}
}
Webservice
private string rTrack;
public List<string> getUpcomingRaces(string track)
{
List<string> f = new List<string>();
rTrack = track;
getUpcomingRacesCallBack((list) =>
{
f = list;
});
return f;
}
private void getUpcomingRacesCallBack(Action<List<string>> callback)
{
List<string> f = new List<string>();
if (checkForNetwork(true) != true)
{
f.Add("No network available");
callback(f);
}
else
{
List<POHWS.webservice.UpcomingRaces> tableData = new List<POHWS.webservice.UpcomingRaces>();
POHWS.webservice.Service1 Service3 = new POHWS.webservice.Service1();
try
{
Service3.BeginGetUpcomingRacesList(rTrack, delegate(IAsyncResult iar)
{
tableData = Service3.EndGetUpcomingRacesList(iar).ToList();
Android.App.Application.SynchronizationContext.Post(delegate
{
if (tableData.Count == 0)
{
f.Add("No Upcoming Races Found within the next 7 days");
callback(f);
}
else
{
for (int i = 0; i < tableData.Count;++i)
f.Add(tableData[i].PostTime);
callback(f);
}
}, null);
}, null);
}
catch (Exception oe)
{
f.Add(oe.ToString());
callback(f);
}
}
}
Is it possible to either stop the UI or delay updating until the webservice
has done what it needs? I've tried quite a lot of things, but nothing gives.

The problem here is that getUpcomingRaces() is acting as if the call to getUpcomingRacesCallBack() is synchronous, and returns the list right away. Since it's very unlikely that the lambda would have fired before the return statement, it is always going to return the empty list.
I would suggest restructuring the code so that it acts on the list only once it's returned, similar to the approach you took with the getUpcomingRacesCallBack() method, which takes in an Action<List<string>>.
If it helps, I have a sample project available here that shows how to use this pattern. I also have a post here that talks about some different approaches to doing work off the UI thread, just in case you end up going the route of making the calls synchronous.

Related

Is it normal that form is stuttering when moving it while a task is running on code side?

I am working on Genetic Algorithyms and I have used a for loop to create childs.The for loop iterates 10 million times and I used it like this.
public void Start()
{
Task.Run(() =>
{
createRandomPopulation();
while (produced < 10000000)
{
int[] nums = tourSelection();
T mother = population[nums[0]];
T father = population[nums[1]];
X[][] gens = CrossingOver<X>.TwoPointCO(mother.Genes, father.Genes);
T child1 = new T();
T child2 = new T();
if (rnd.Next(1, 101) <= habitat.MutationOdd)
{
Mutation<X>.SimpleMutation(gens[0]);
Mutation<X>.SimpleMutation(gens[1]);
}
child1.Genes = gens[0];
child2.Genes = gens[1];
testFitness(child1);
testFitness(child2);
population[nums[nums.Length - 1]] = child1;
population[nums[nums.Length - 2]] = child2;
produced += 2;
double temp = double.Parse((produced * 100 / 10000000).ToString("0.00"));
progress = temp;
asyncOperation.Post(new SendOrPostCallback(delegate
{
if (ProgressChanged != null)
ProgressChanged(this, EventArgs.Empty);
}), null);
}
});
}
void createRandomPopulation()
{
for (int i = 0; i < habitat.MaxPopulationNumber; i++)
{
T desc = new T();
if (!habitat.Repeat)
{
List<X> tempGenes = new List<X>(genes);
desc.Genes = new X[genes.Length];
for (int j = 0; j < habitat.Length; j++)
{
int k = rnd.Next(0, tempGenes.Count);
desc.Genes[j] = tempGenes[k];
tempGenes.RemoveAt(k);
}
}
else
{
for (int j = 0; j < habitat.Length; j++)
{
desc.Genes[j] = genes[rnd.Next(0, genes.Length)];
}
}
population[i] = desc;
produced++;
double temp = double.Parse((produced * 100 / 10000000).ToString("0.00"));
progress = temp;
asyncOperation.Post(new SendOrPostCallback(delegate
{
if (ProgressChanged != null)
ProgressChanged(this, EventArgs.Empty);
}), null);
testFitness(population[i]);
}
}
But while this code is running, the application UI responses late and is stuttering. If I add Application.DoEvents() inside for loop the problem is gone. I was expecting that using Task prevent such problems to occur, so this is unexpected for me. How can I fix this issue in a good way?
It is not normal. There are 2 possible reasons I see here:
Significant CPU overload. You may do well to move the running threads to a lower priority in the task (store the old priority, then reset it to this when the task is finished).
Something in the code is going back to the UI thread, causing a lot of stress there. This would be in the code you do not show us.
You are posting too much progress change notifications to the UI thread.
In order to fix that in a good way, you can apply several optimizations. But let first extract the progress updating code into a separate method (which is good anyway since you have 2 places using that logic):
private void UpdateProgress()
{
double temp = double.Parse((produced * 100 / 10000000).ToString("0.00"));
progress = temp;
asyncOperation.Post(new SendOrPostCallback(delegate
{
if (ProgressChanged != null)
ProgressChanged(this, EventArgs.Empty);
}), null);
}
I see three possible optimizations of skipping the Post call.
(1) When progress == temp
(2) When ProgressChanged == null
(3) When Post has been called, but not yet processed.
First two are obvious and easy to be implemented. The third one requires additional state - flag and thread synchronization.
Here is one possible implementation:
private object updateProgressLock = new object();
private bool updateProgressRequest = false;
private void UpdateProgress()
{
double temp = double.Parse((produced * 100 / 10000000).ToString("0.00"));
if (progress == temp) return; // (1)
progress = temp;
if (ProgressChanged == null) return; // (2)
lock (updateRequestLock)
{
if (updateRequest) return; // (3)
asyncOperation.Post(new SendOrPostCallback(delegate
{
lock (updateRequestLock)
updateRequest = false;
if (ProgressChanged != null)
ProgressChanged(this, EventArgs.Empty);
}), null);
updateRequest = true;
}
}
You are probably not calling that part of code in async way but you havent showed enough code to poibt a finger to prticular line where the problem is.
this article might help you: http://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-dont-use.html
edit:
i have seen your edit...
your event handler should be decorated with 'async'. it should als call await Start(). and the last, your start() method should return task (from return task.run() )
public async Task Start()
{
return await Task.Run()...
}

How to Clone a Windows Forms Controls even with non-Serializable properties?

How to Clone or Serialize a Windows Forms Control?
When I am trying to Clone windows forms controls using this code "CloneControl(Control ct1)", it allows me to duplicate controls with some Serializable properties, not with all properties.
public Form1()
{
InitializeComponent();
Columns = new DataGridViewTextBoxColumn[2];
for (int i = 0; i < 2; i++)
{
Columns[i] = new System.Windows.Forms.DataGridViewTextBoxColumn();
//
// Columns[i]
//
Columns[i].HeaderText = "j" + (i + 1);
Columns[i].Name = "Column" + (i + 1);
Columns[i].Width = 50;
}
dataGridView1 = new System.Windows.Forms.DataGridView();
dataGridView1.Name = "dataGridView1";
dataGridView1.Location = new System.Drawing.Point(100, 100);
dataGridView1.RowHeadersWidth = 50;
dataGridView1.RowTemplate.Height = 25;
dataGridView1.Size = new System.Drawing.Size(55 + 50 * 2, 25 + dataGridView1.RowTemplate.Height * 2);
dataGridView1.Anchor = System.Windows.Forms.AnchorStyles.None;
dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
dataGridView1.Columns.AddRange(Columns);
dataGridView1.TabIndex = 3;
dataGridView1.AllowUserToAddRows = false;
dataGridView1.Rows.Add();
dataGridView1.Rows.Add();
dataGridView1.Rows[0].HeaderCell.Value = "i" + 1;
dataGridView1.Rows[1].HeaderCell.Value = "i" + 2;
dataGridView1.Rows[0].Cells[0].Value = "value1";
Controls.Add(dataGridView1);
Control cloned1 = CloneControl(dataGridView1);
cloned1.SetBounds(cloned1.Location.X, cloned1.Location.Y + 300, cloned1.Width, ct1.Height);
Controls.Add(cloned1);
cloned1.Show();
}
public Control CloneControl(Control ct1)
{
Hashtable PropertyList = new Hashtable();
PropertyDescriptorCollection Properties = TypeDescriptor.GetProperties(ct1);
Assembly controlAsm = Assembly.LoadWithPartialName(ct1.GetType().Namespace);
Type controlType = controlAsm.GetType(ct1.GetType().Namespace + "." + ct1.GetType().Name);
Control cloned1 = (Control)Activator.CreateInstance(controlType);
foreach (PropertyDescriptor pr1 in Properties)
{
if (pr1.PropertyType.IsSerializable)
{
PropertyList.Add(pr1.Name, pr1.GetValue(ct1));
}
if (PropertyList.Contains(pr1.Name))
{
try
{
Object obj = PropertyList[pr1.Name];
pr1.SetValue(cloned1, obj);
}
catch (Exception ex)
{
}
}
}
return ct2;
}
If you run the code... the you will get
As you can see in the main method I create a clone of dataGridView1, which has a few properties.
And actually each cell value is null in a cloned dataGridView.
Also size of a columns are not cloned!
You may have a question: if Visual Studio or SharpDeveloper as IDE which is written in C# can handle this problem, then it might be possible to write that kind of code! Right?
In Visual Studio When you are trying drag and drop controls, or copy and paste controls, it not only duplicates that controls with all properties (including Serializable or non-Serializable) but also it changes the name of control itself from "dataGridView1" to "dataGridView2" as well as in SharpDeveloper!
What should I do?
What kind of method should I create?
Maybe another control has a many non-Serializable properties!
How to duplicate all of them?
Please anyone.....
Like #Hans mentioned in the comment, Clone is not that easy. If you want to get some identical controls with only a bit different, you'd better use a function to define general behavior and pass the different properties in as parameters. For example, we define a function with some general properties which apply to DataGridView:
private void InitDataGridView(DataGridView dataGridView, string name)
{
dataGridView.Name = name;
// configure other properties here
dataGridView.Location = new System.Drawing.Point(100, 100);
dataGridView.RowHeadersWidth = 50;
dataGridView.RowTemplate.Height = 25;
dataGridView.Size = new System.Drawing.Size(55 + 50 * 2, 25 + dataGridView1.RowTemplate.Height * 2);
dataGridView.Anchor = System.Windows.Forms.AnchorStyles.None;
dataGridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
// remember to initialize your columns, or pass it in as a parameter
dataGridView.Columns.AddRange(Columns);
dataGridView.AllowUserToAddRows = false;
dataGridView.Rows.Add();
dataGridView.Rows.Add();
dataGridView.Rows[0].HeaderCell.Value = "i" + 1;
dataGridView.Rows[1].HeaderCell.Value = "i" + 2;
dataGridView.Rows[0].Cells[0].Value = "value1";
}
public Form1()
{
InitializeComponent();
var dataGridView1 = new DataGridView();
var dataGridView2 = new DataGridView();
InitDataGridView(dataGridView1, "dataGridView1");
InitDataGridView(dataGridView2, "dataGridView2");
}
IDE (e.g. Visual Studio) is using PropertyDescriptors, DesignerSerializationVisibility and ShouldSerializeValue, but DataGrid Rows are something special, because you cannot add them at design time! IDE cannot copy something that is not there, so, the solution must be different (if you want to clone controls beyond what IDE/Designer can do - see other answers and comments for this). Try my code (everything except grid rows got cloned without the extra check - the columns got cloned).
foreach(PropertyDescriptor pd in TypeDescriptor.GetProperties(src)) {
if(!pd.ShouldSerializeValue(src)) {
if(src is DataGridView && pd.Name == "Rows")
CopyDataGridRows((DataGridView)src, (DataGridView)dst);
continue; }
Note: The above can be done better (by check for the class at the end), but is as it is to be obvious.
using System;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
namespace CloneControls {
public partial class Form1: Form {
public Form1() { InitializeComponent(); }
private void Form1_Load(object sender, EventArgs e) {
dataGridView1.Rows.Add();
dataGridView1.Rows.Add();
foreach(Control c in splitContainer1.Panel1.Controls)
splitContainer1.Panel2.Controls.Add((Control)Clone(c));
}
static object Clone(object o) {
return Copy(o, Activator.CreateInstance(o.GetType()));
}
static object Copy(object src, object dst) {
IList list = src as IList;
if(list != null) {
IList to = dst as IList;
foreach(var x in list)
to.Add(Clone(x));
return dst; }
foreach(PropertyDescriptor pd in TypeDescriptor.GetProperties(src)) {
if(!pd.ShouldSerializeValue(src)) {
if(src is DataGridView && pd.Name == "Rows")
CopyDataGridRows((DataGridView)src, (DataGridView)dst);
continue; }
switch(pd.SerializationVisibility) {
default: continue;
case DesignerSerializationVisibility.Visible:
if(pd.IsReadOnly) continue;
pd.SetValue(dst, pd.GetValue(src));
continue;
case DesignerSerializationVisibility.Content:
Copy(pd.GetValue(src), pd.GetValue(dst));
continue;
}
}
return dst;
}
static void CopyDataGridRows(DataGridView src, DataGridView dst) {
foreach(DataGridViewRow row in src.Rows)
if(!row.IsNewRow) dst.Rows.Add((DataGridViewRow)Clone(row));
}
}
}
I think I made more better method here.
This Method at first checks interface of property: if it is ICollection then it does the first job.
After this one loop ends in the method "DeepClone()", then it is necessary to do another loop without checking PropertyType Interface... I mean I could not mix these two operation into one loop?!
Also You can detect that there will be some kind of Run-time Exceptions and for this reason I put this code into try-catch block...
Control cloned1 = (Control)DeepClone(dataGridView1);
cloned1.SetBounds(cloned1.Location.X, cloned1.Location.Y + 300, cloned1.Width, ct1.Height);
Controls.Add(cloned1);
cloned1.Show();
public dynamic DeepClone(dynamic ob1)
{
dynamic ob2 = null;
if (ob1.GetType().IsSerializable && !ob1.GetType().IsArray)
{
if (ob1 != null)
{
using (MemoryStream ms = new MemoryStream())
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(ms, ob1);
ms.Position = 0;
ob2 = formatter.Deserialize(ms);
}
}
}
else
{
if (ob1.GetType().IsArray)
{
var r1 = ob1.Rank;
object[] d1 = new object[r1];
long[] V1 = new long[r1];
for (int i = 0; i < r1; i++)
{
V1[i] = 0;
d1[i] = ob1.GetUpperBound(i) + 1;
}
ob2 = Activator.CreateInstance(ob1.GetType(), d1);
for (long i = 0; i <= ob2.Length; i++)
{
ob2.SetValue(DeepClone(ob1.GetValue(V1)), V1);
for (int j = 0; j <= V1.GetUpperBound(0); j++)
{
if (V1[j] < ob2.GetUpperBound(j))
{
V1[j]++;
break;
}
else
{
V1[j] = 0;
}
}
}
}
else
{
PropertyInfo[] P1 = ob1.GetType().GetProperties();
ob2 = Activator.CreateInstance(ob1.GetType());
foreach (PropertyInfo p1 in P1)
{
try
{
if (p1.PropertyType.GetInterface("System.Collections.ICollection", true) != null)
{
dynamic V2 = p1.GetValue(ob1) as IEnumerable;
MethodInfo gm1 = p1.PropertyType.GetMethods().Where(m => m.Name == "Add").Where(p => p.GetParameters().Count() == 1).First(f => V2[0].GetType().IsSubclassOf(f.GetParameters()[0].ParameterType) || f.GetParameters()[0].ParameterType == V2[0].GetType());
if (V2[0].GetType().IsSubclassOf(gm1.GetParameters()[0].ParameterType) || gm1.GetParameters()[0].ParameterType == V2[0].GetType())
{
for (int i = 0; i < V2.Count; i++)
{
dynamic V3 = DeepClone(V2[i]);
gm1.Invoke(p1.GetValue(ob2), new[] {V3});
}
}
}
}
catch (Exception ex)
{
}
}
foreach (PropertyInfo p1 in P1)
{
try
{
if (p1.PropertyType.IsSerializable && p1.CanWrite)
{
var v2 = p1.GetValue(ob1);
p1.SetValue(ob2, v2);
}
if (!p1.PropertyType.IsSerializable && p1.CanWrite)
{
dynamic V2 = p1.GetValue(ob1);
if (p1.PropertyType.GetMethod("Clone") != null)
{
dynamic v1 = V2.Clone();
p1.SetValue(ob2, v1);
}
}
}
catch (Exception ex)
{
}
}
}
}
return ob2;
}
You may say that this Method does not copy some kind of property, But it does copy of main properties and the Cloned control will look like an original control!
Trying to clone a control is overkill except if you really need a totally generic control clone method. Most of the time, you only need to clone a specific control and you have an easy access to the code that created it (see the Form designer generated code, and the setup code you wrote yourself).
But nevertheless, I once used a trick to duplicate many controls at once in order to fill the new tabs of a TabControl, choosing one out of ten tab designs.
I also wanted to use the Form design tool of the C# IDE to edit and modify the 10 template.
So, besides my Tab control form, and using the VS IDE, I created 10 "control factory dummy forms" in my project. I put a dummy Panel control in each of it.
Each time I had to dynamically create a new Tab, I simply instantiated a new dummy window of the desired style. Then I simply moved the Parent pane to my ControlTab (using the Controls.Add() method of the new tab).
This way, you must link the event handlers after the Tab creation (after the controls move). And the event handler's code should be written in you main window class, otherwise you will have "this" reference problems.
Obviously, you will have to store control references somewhere, to be able to access them. The easiest way to do this is to just keep track of each "dummy template Form" you instantiate and to set the "modifier" of your controls to be "public". You can use the Tag property of the destination tab page to store that reference. But, to avoid many casts, it is better to declare an array of each form class, and to store the references there.

List<T> moving to the next element

I searched SO and found some posts, but could not get them to work.
Question: How would I loop to the next item in my List Collection (custLoginHist[1] etc)?
List<eCommCustomer.oCustomer> custLoginHist = new List<eComm.oCustomer>();
eCommCustomerDAL.GetCustomerPrevLogin(custLoginHist, oCust);
if (custLoginHist.Count > 0)
{
eCommSecurityFactory oSecFactory = new eCommSecurityFactory();
if (oCust.CustHash == oSecFactory.CreateHash(custLoginHist[0].CustSalt, custLoginHist[0].CustHash))
{
//Password has been used before;
return false;
}
else
{
// Valid password;
return true;
}
}
return true;
}
foreach(eCommCustomer.oCustomer cust in custLoginHist)
{
//Do something with cust here.
}
OR:
for(int i = 0; i != custLoginHist.Count; ++i)
{
eCommCustomer.oCustomer cust = custLoginHist[i];
//Do something with cust here.
}
In this case, we want to return false for any single match, and true otherwise, so:
foreach(eCommCustomer.oCustomer cust in custLoginHist)
if(oCust.CustHash == oSecFactory.CreateHash(custLoginHist[0].CustSalt, custLoginHist[0].CustHash)
return false;
return true;//if we reached here, no matches.
This is a bad idea though, because you've made breaking into the system easier. If I try to set my password to something, and you refuse, I now know that one of your users uses that password. You are much better off letting this case happen, though you should perhaps be blocking some of the more likely offenders ("password", "password1", etc) with a quality check.
List<eCommCustomer.oCustomer> custLoginHist = new List<eComm.oCustomer>();
eCommCustomerDAL.GetCustomerPrevLogin(custLoginHist, oCust);
foreach (var custLogin in custLoginHist)
{
eCommSecurityFactory oSecFactory = new eCommSecurityFactory();
if (oCust.CustHash == oSecFactory.CreateHash(custLogin.CustSalt, custLogin.CustHash))
{
//Password has been used before;
return false;
}
}
return true;
Try something like this, maybe you have to customize your return statements but it should give you an insight how it works.
foreach(var item in yourList)
{
//Iterate;
}
If you want break , you can use : break;
If you want finish you can use : continue;
List<T> implements IEnumerable<T>, so you can just use foreach or if you to be able to edit T in the loop, you can use for.
foreach(var item in custLoginHist)
{
}
Or
for (int i = 0; i < custLoginHist.Count; i++)
{
}
Then if you need to exit out of the loop before it is completed (such as if you have a condition that is true, you can just use break; to exit the loop, or you can return from a loop too if you want to return a value.
You can you loop for this. For example foreach or for:
foreach (var custLogin in custLoginHist)
{
eCommSecurityFactory oSecFactory = new eCommSecurityFactory();
if (oCust.CustHash == oSecFactory.CreateHash(custLogin.CustSalt, custLogin.CustHash))
{
//Password has been used before;
return false;
}
else
{
// Valid password;
return true;
}
}
List<eCommCustomer.oCustomer> custLoginHist = new List<eComm.oCustomer>();
eCommCustomerDAL.GetCustomerPrevLogin(custLoginHist, oCust);
return custLoginHist.Select(c=>oSecFactory.CreateHash(c.CustSalt,c.CustHash))
.Any(x=>x==oCust.CustHash)

Asynchronous event in c#

In my application I have three asynchronous events.
After all of them are complete I need to call some Method1().
How can I implement this logic?
Update
Here is one of my asynchronous events:
public static void SetBackground(string moduleName, Grid LayoutRoot)
{
var feedsModule = FeedHandler.GetInstance().ModulesSetting.Where(type => type.ModuleType == moduleName).FirstOrDefault();
if (feedsModule != null)
{
var imageResources = feedsModule.getResources().getImageResource("Background") ??
FeedHandler.GetInstance().MainApp.getResources().getImageResource("Background");
if (imageResources != null)
{
//DownLoad Image
Action<BitmapImage> onDownloaded = bi => LayoutRoot.Background = new ImageBrush() { ImageSource = bi, Stretch = Stretch.Fill };
CacheImageFile.GetInstance().DownloadImageFromWeb(new Uri(imageResources.getValue()), onDownloaded);
}
}
}
Bit field (or 3 booleans) set by each event handler. Each event handler checks that the condition is met then calls Method1()
tryMethod1()
{
if (calledEvent1 && calledEvent2 && calledEvent3) {
Method1();
calledEvent1 = false;
calledEvent2 = false;
calledEvent3 = false;
}
}
eventHandler1() {
calledEvent1 = true;
// do stuff
tryMethod1();
}
Not given any other information, what will work is to use a counter. Just an int variable that is initialized to be 3, decremented in all handlers and checked for equality to 0 and that case go on.
You should use WaitHandles for this. Here is a quick example, but it should give you the basic idea:
List<ManualResetEvent> waitList = new List<ManualResetEvent>() { new ManualResetEvent(false), new ManualResetEvent(false) };
void asyncfunc1()
{
//do work
waitList[0].Set();
}
void asyncfunc2()
{
//do work
waitList[1].Set();
}
void waitFunc()
{
//in non-phone apps you would wait like this:
//WaitHandle.WaitAll(waitList.ToArray());
//but on the phone 'Waitall' doesn't exist so you have to write your own:
MyWaitAll(waitList.ToArray());
}
void MyWaitAll(WaitHandle[] waitHandleArray)
{
foreach (WaitHandle wh in waitHandleArray)
{
wh.WaitOne();
}
}

Update datagridview from background thread odd behavior

I have some data, and I update it in a task: The app is a hack at the moment for an idea so apologies for the code.
Task.Factory.StartNew(() =>
{
dataGridView1.BeginInvoke((Action)(() =>
{
dataGridView1.SuspendLayout();
}));
dataSet1.Reset();
da.Fill(dataSet1);
dataGridView1.BeginInvoke((Action)(() =>
{
dataGridView1.DataSource = dataSet1.Tables[0];
dataGridView1.Columns[0].Visible = false;
dataGridView1.Columns[1].Width = 50;
dataGridView1.ResumeLayout();
}));
}
).ContinueWith(task =>
{
if (dataSet1.Tables[0].Rows.Count > 0)
{
if (lastcount != dataSet1.Tables[0].Rows.Count)
{
lastcount = dataSet1.Tables[0].Rows.Count;
if (lastcount == 0)
{
NotifyWithMessage("The items have been cleared", "Items cleared");
}
else
{
NotifyWithMessage(String.Format("There are {0} new items in your monitor", dataSet1.Tables[0].Rows.Count));
}
}
}
}
);
Now, the code fundamentally works. No errors, which is nice..
When it was updated outside a task, there was no flashing of the datavgridview at all, when I run it in debug, it's very minor and well within acceptable for the hack.. the moment I run it outside debugging... it's terribly obvious! The suspend and resume layouts have not made any difference at all. I need the code in the thread, because the UI is lumpy responsive without - whereas it's acceptable, but it has the bad refresh now.
My Datagridview is custom coloured depending on cell colours, but, I just cant see why there's the difference between debug and release, I'd expect the performance the other way round!
(I tried Invoke and BeginInvoke...)
I looked at Horrible redraw performance of the DataGridView on one of my two screens
And under debug, this doesn't flicker at all, not even a bit... Under release conditions, there is a ridiculous flicker...
What can I do?
Kick off a task that fills the dataset in the background, and when this task is done, you do a BeginInvoke, where you Suspend the layout, assign the data and resume.
With the version you have now, there are soo many possibilities when which the code path is executed that it's hard to predict what will happen.
The rendering has to be on the UI thread, so all you can do is to try to optimize the code for it. And the Async part I'd do as described at the beginning of the post.
In the end heres what I did:
I rean the query into a new dataset, if the count was the same, then I didnt update the grid, if the count had changed I did.
timer1.Stop();
Task<Boolean>.Factory.StartNew(() =>
{
DataSet t = new DataSet();
//_dataSet1.Reset();
Boolean ok = false;
Int16 retries = 0;
while (!ok && retries<3)
try
{
da.Fill(t);
ok = true;
}
catch
{
retries++;
Thread.Sleep(1000);
}
//if (!ok) throw new Exception("DB error");
if (!ok) return false;
try
{
if (t.Tables.Count > 0 && t.Tables[0].Rows.Count != _lastcount)
{
_dataSet1 = t;
_lastcount = t.Tables[0].Rows.Count;
return true;
}
}
catch { }
return false;
}).ContinueWith(task =>
{
if (task.IsFaulted)
{
SQLFailed();
return;
}
if (!task.Result) return;
Invoke((Action) (() =>
{
dataGridView1.DataSource = _dataSet1.Tables[0];
dataGridView1.Columns[0].Visible = false;
dataGridView1.Columns[1].Width = 50;
}));
if (_lastcount == 0)
{
NotifyWithMessage("The items have been cleared", "Items cleared");
}
else
{
NotifyWithMessage(String.Format("There are {0} new items in your monitor", _lastcount));
}
});
timer1.Start();

Categories

Resources