Asynchronous Call Best Practice - c#

I am developing a windows application with vs.NET 2010 and C# windows forms. This app has an user control that queries a service(WCF hosted on win service) and needs to do this without blocking the UI. The user control contains a grid that will show that results. I think that my situation is most common. My question to you is what can be done with C# in order for the following code to run smoother and with a better error handling. I am using MehtodInvoker so I can avoid writeing two seprate methods for this call - wait - fill scenario.
public void LoadData()
{
StartWaitProgress(0);
ThreadPool.QueueUserWorkItem(x =>
{
try
{
MyDocMail[] mails;
var history = Program.NoxProxy.GetDocumentHistory(out mails, Program.MySessionId, docId);
this.Invoke(new MethodInvoker(delegate()
{
this.SuspendLayout();
gridVersions.Rows.Clear();
foreach (var item in history)
{
gridVersions.Rows.Add();
int RowIndex = gridVersions.RowCount - 1;
DataGridViewRow demoRow = gridVersions.Rows[RowIndex];
demoRow.Tag = item.Id;
if (gridVersions.RowCount == 1)
{
demoRow.Cells[0].Value = Properties.Resources.Document_16;
}
demoRow.Cells[1].Value = item.Title;
demoRow.Cells[2].Value = item.Size.GetFileSize();
demoRow.Cells[3].Value = item.LastModified;
demoRow.Cells[4].Value = item.CheckoutBy;
demoRow.Cells[5].Value = item.Cotegory;
}
gridEmails.Rows.Clear();
foreach (var item in mails)
{
gridEmails.Rows.Add();
int RowIndex = gridEmails.RowCount - 1;
DataGridViewRow demoRow = gridEmails.Rows[RowIndex];
demoRow.Tag = item.Id;
demoRow.Cells[1].Value = item.From;
demoRow.Cells[2].Value = item.To;
demoRow.Cells[3].Value = item.Date;
}
this.ResumeLayout();
}));
}
catch (Exception ex)
{
Program.PopError(ex);
this.Invoke(new MethodInvoker(delegate() { this.Close(); }));
}
finally { this.Invoke(new MethodInvoker(delegate() { StopWaitProgress(); })); }
});
}

There's nothing wrong with your solution, although you can accomplish it more easily with BackgroundWorker.
BackgroundWorker handles thread exceptions, calling Invoke on the WPF window, and helps with progress reporting and cancellation. More examples here.
P.S. Future versions of C# may make this even easier - check out the Async CTP.

Related

Accessing UI from outside thread in Winforms

In WPF, one could use something like:
Application.Current.Dispatcher.BeginInvoke(new Action(() => Form1.grid.Items.Refresh()));
to access UI functions outside of the main thread. In Winforms however, the same functionality isn't there. What would be the easiest way to access a BindingList that exists inside of my Form1 class from my "working" thread? Currently I getting the following error when trying to access "Form1.record_list":
System.InvalidOperationException: Cross-thread operation not valid: Control '' accessed from a thread other than the thread it was created on.
edit: I appreciate the help so far, but I'm lost on "this.Invoke". My method in the separate thread has no "Invoke".
Here's an example of my code so far.
public static void listen(IPEndPoint server_ip)
{
Console.WriteLine("In listen");
while (true)
{
try
{
byte[] received_bytes = udp_client.Receive(ref server_ip);
string received_data = Encoding.ASCII.GetString(received_bytes);
Record record = JsonConvert.DeserializeObject<Record>(received_data);
Form1.record_list.Add(record); //This is where I assume the problem spawns
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
}
public partial class Form1 : Form
{
public static BindingList<Record> record_list = new BindingList<Record> { };
public static DataGridViewCellStyle style = new DataGridViewCellStyle();
public Form1()
{
InitializeComponent();
Thread thread = new Thread(SCMClient.connect);
thread.IsBackground = true;
thread.Start();
FillData();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
foreach (DataGridViewRow row in dataGridView.Rows)
{
for (var i = 0; i < row.Cells.Count; i++)
{
Console.WriteLine(row.Cells[i].Value);
if (row.Cells[i].Value as string == "OK")
{
row.Cells[i].Style.BackColor = Color.Red;
Console.WriteLine("IF WAS TRUE");
}
}
}
}
I think the specific problem here is when I add Records to the Forms1.record_list. I'm not sure how to add items to that list without causing the Cross-thread error...
You MUST access your UI from your UI Thread only. But you can use the Control.Invoke method - a Form IS a Control - to ensure your code is run from the UI Thread.
Also, you have a static BindingList, but I assume you want to display the contents of that list in an specific form. You should make the BindingList an instance member instead... or get a reference to a valid Form. The Control.Invoke method is not static.
There are several ways to do so. I would do it like so:
First, create a method in your Form class that adds the record to the list.
public void AddRecord(Record r) {
if(this.InvokeRequired) {
this.Invoke(new MethodInvoker(() => this.AddRecord(r)));
} else {
this.record_list.Add(r);
}
}
Second, you need to have a reference to the form (in the next step, that is the theForm variable).
Then, in your listener method, invoke AddRecord method instead of adding the record in your BindingList directly.
public static void listen(IPEndPoint server_ip)
{
Console.WriteLine("In listen");
while (true)
{
try
{
byte[] received_bytes = udp_client.Receive(ref server_ip);
string received_data = Encoding.ASCII.GetString(received_bytes);
Record record = JsonConvert.DeserializeObject<Record>(received_data);
theForm.AddRecord(record); // You need a Form instance.
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
}
The below will work out in winforms. Have tried once. The below code helps to update the label in UI thread from another thread.
string _string = "Call from another thread";
this.Invoke((MethodInvoker)delegate {
label1.Text = _string;
});
You're looking for SynchronizationContext.Current, which works with both WinForms and WPF.
Note that you'll need to grab its value from the UI thread, since it's per-thread.

Cross-thread operation not valid. Multiple solutions fail to work,

Before reading, I want everyone reading this to know that I have tried multiple delegate/Cross-Threading/Invoking Solutions from all over stack overflow.
With that said, this is what my program is supposed to do:
Worker Thread 1 is called to start Async Operation.
If it detects a line that has a typical PRIVMSG header along with the word subscribed!
Create a new MetroTaskWindow with a TaskWindowControl and Add it to the queue
Worker Thread 2 is called after worker thread 1
Worker Thread 2 checks every 5 seconds if queue contains something
If it does, show it and get rid of it
Here is the associated Code If you need more, let me know to the above requirements:
Worker Thread 1 Segment
string line = "";
while (!backgroundWorker1.CancellationPending)
{
try
{
line = reader.ReadLine();
}
catch { }
if (line != null && !line.Contains("JOIN"))
{
try
{
if (line.Contains("PING") && !line.Contains("PRIVMSG"))
{
writer.Write(line.Replace("PING", "PONG"));
Trace.WriteLine(line.Replace("PING", "PONG"));
}
else if (line.Split(new char[] { ' ' })[0].Equals(":twitchnotify!twitchnotify#twitchnotify.tmi.twitch.tv") ||
line.Split(new char[] { ' ' })[0].Equals(":stds_catchemall!stds_catchemall#stds_catchemall.tmi.twitch.tv") && line.Contains("subscribed!"))
{
total += 1;
checkNotifications();
}
}
catch
{
continue;
}
}
if (!String.IsNullOrEmpty(line))
Trace.WriteLine(line);
}
}
private void checkNotifications()
{
List<Achievement> tempQueue = new List<Achievement>();
foreach (Achievement a in achievements) {
//I know i could shorten this, but i need it left like this...
if (a.AfterSub)
tempQueue.Add(a);
if (total - a.Goal == start)
tempQueue.Add(a);
if (total == a.Goal)
tempQueue.Add(a);
}
foreach (Achievement a in Sort(tempQueue))
{
MetroTaskWindow m = new MetroTaskWindow(a, this, a.Type.ToString(), new TaskWindowControl(a.Name, a.Message, a), 4, r, ((ScreenRegion)r).getGS());
queue.Add(m);
}
}
Worker Thread 2
private void CheckAvailable_DoWork(object sender, DoWorkEventArgs e)
{
while (true)
{
Thread.Sleep(5250);
BeginInvoke((MethodInvoker)delegate
{
if (queue.Count > 0)
{
queue[0].Show(); // <---- Error Occurs Here
//Cross-thread operation not valid: Control 'TaskWindowControl' accessed from a thread other than the thread it was created on.
queue.RemoveAt(0);
}
});
}
}
Metro Task Window
public MetroTaskWindow(Achievement a, IWin32Window parent, string title, Control userControl, int secToClose, MetroForm r, Form gs)
{
controlContainer = new MetroPanel();
Controls.Add(controlContainer);
controlContainer.Controls.Add(userControl);
userControl.Dock = DockStyle.Fill;
closeTime = secToClose * 500;
this.a = a;
form = r;
chroma = gs;
p = (Form1)parent;
this.Text = title;
this.Resizable = false;
this.Movable = true;
this.StartPosition = FormStartPosition.Manual;
if (parent != null && parent is IMetroForm)
{
this.Theme = ((IMetroForm)parent).Theme;
this.Style = ((IMetroForm)parent).Style;
this.StyleManager = ((IMetroForm)parent).StyleManager.Clone(this) as MetroStyleManager;
this.ShadowType = MetroFormShadowType.None;
}
switch (a.Type)
{
case PopupType.Achievement:
Text = "Achievement!";
break;
case PopupType.Milestone:
Text = "Milestone!";
break;
case PopupType.Notification:
Text = "Notification";
break;
}
}
TaskWindowControl
public partial class TaskWindowControl : UserControl
{
public TaskWindowControl(string name, string info, Achievement a)
{
InitializeComponent();
metroLabel1.Text = name;
metroTextBox1.Text = info;
metroTextBox1.Select(0, 0);
try
{
Trace.WriteLine(Directory.GetCurrentDirectory() + "\\" + a.Picture);
pictureBox1.Image = Image.FromFile(Directory.GetCurrentDirectory() + "\\" + a.Picture);
}
catch
{
MessageBox.Show("There was an error loading the image for this achievement.");
}
}
}
And as I stated above, there are a LOT of duplicates, none of which have helped my answer. I also don't know much about the Delegate/Invoking process which is why I need some extra help.
Update #1
Anywhere I had queue.Add(m); is now replaced with queue.Enqueue(a);
And my update method (Without timer so far) is just:
public void DisplayDialog()
{
Achievement a = null;
queue.TryDequeue(out a);
MetroTaskWindow m = new MetroTaskWindow(a, this, a.Type.ToString(), new TaskWindowControl(a.Name, a.Message, a), 4, r, ((ScreenRegion)r).getGS());
m.Show();
}
Something that I found is that when I changed the code to this, the Thread that does the animations and things on my MetroTaskWindow doesn't get activated. The Windows Stays in with a windows loading circle and it never goes away. Any ideas? I'm using the OnActivated event, so it SHOULD fire when i .Show();
Edit #2
What I ended up doing to get my above error to work, was to switch all of my code to a new Windows Form Timer. This polls every 5 seconds, and keeps the MetroTaskWindow from hanging due to the while loop.
Form1.Designer.cs
private System.Windows.Forms.Timer timer2;
timer2 = new System.Windows.Forms.Timer(this.components);
timer2.Interval = 5250;
timer2.Tick += new System.EventHandler(this.timer2_Tick);
Form1.cs
private void timer2_Tick(object sender, EventArgs e)
{
if (queue.Count > 0)
{
DisplayDialog();
}
}
The error makes sense. The queue contains MetroTaskWindow instances created on a worker thread, not on the UI thread. You call checkNotifications in the background worker's entry point method, which runs on a separate thread.
What I recommend you do is:
Store in the queue only metadata about the windows. Create a new class with name, message and anything MetroTaskWindow needs to be created. Add instances of this class to the queue in checkNotifications. By the looks of it you might be able to use the Achievement class directly and push that to the queue.
Create the windows and show them in CheckAvailable_DoWork, where you now call queue[0].Show();.
After you solve this you should post a new question about how to refactor your code into async/await and get rid of that ugly background worker.
A quick refactoring idea
I would remove the second background worker altogether and use a timer instead that polls the queue every X seconds (or 5250ms, if you prefer).
To make this work you need to change the queue's type which is now actually a List<T> to a ConcurrentQueue<T>. This will allow you to push stuff from the worker, and pop from the UI thread (in the timer callback).
This way you can remove the second background worker and that while(true).

LinkedList modified, Thread crashing the program

My problem is a synchronization problem with a thread and the user simultaneously accessing and modifying a LinkedList.
I’m making a program in C# that will display some messages in a panel. I’m getting an error called “The collection was modified after the enumerator was instantiated.”, that is because I’m adding or removing messages while a thread is accessing the LinkedList.
I have read some solutions but I am still unable to make them work. I’m using an Enumerator for the thread work in my LinkedList. I tried to make some locks in my code so the thread would not iterate the list while I remove or add an element. I also tried to lock the thread for the operations on my list. But all my attempts failed.
Here is some code of my project. This one is for adding a message:
public void addMsg(MsgForDisplay msg) {
Label lbl = new Label();
lbl.Text = (msg.getMsgText() + " -");
lbl.ForeColor = color;
lbl.Font = textFont;
lbl.BackColor = backg;
lbl.Visible = true;
lbl.AutoSize = true;
lbl.Location = new Point(width(), 0);
//lock(labels) { tried to lock here but failed
labels.AddLast(lbl);
lastLb = lbl;
this.Controls.Add(lbl);
this.Refresh();
//}
}
Removing a message:
public void removeMsg(string msg) {
string remove = msg + " -";
Label lbRemove = null;
//lock(labels) { also tried to lock here
var it = labels.GetEnumerator();
while(it.MoveNext()) {
Label label = it.Current;
if (label.Text.Equals(remove)) {
lbRemove = label;
}
}
labels.Remove(lbRemove);
this.Controls.Remove(lbRemove);
this.Refresh();
//}
}
And there is the problem, in my thread:
public void run() {
while (true) {
// lock (labels) { also tried to lock here
var it = labels.GetEnumerator();
while (it.MoveNext()) { // the crash occurs here
Label lb = it.Current;
if (lb.Location.X + lb.Width < 0) {
this.Invoke(new MethodInvoker(() => { this.Controls.Remove(lb); }));
if (labels.Count > 1)
this.Invoke(new MethodInvoker(() => { lb.Location = new Point(lastLb.Right, 0); }));
else
this.Invoke(new MethodInvoker(() => { lb.Location = new Point(2000, 0); }));
lastLb = lb;
this.Invoke(new MethodInvoker(() => { this.Controls.Add(lb); }));
this.Invoke(new MethodInvoker(() => { this.Refresh(); }));
}
if (leftLb != null)
if (leftLb.Location.X + leftLb.Width - lb.Location.X < -20)
this.Invoke(new MethodInvoker(() => { lb.Location = new Point(leftLb.Right, 0); }));
else
this.Invoke(new MethodInvoker(() => { lb.Location = new Point(lb.Location.X - 3, lb.Location.Y); }));
leftLb = lb;
}
System.Threading.Thread.Sleep(30);
// }
}
}
As you can see I’m using an GetEnumerator of my labels, what in Java should be the Iterator. With this I shouldn’t be able to iterate the list without problem when the user add or remove messages?
Is there a way to synchronize the accesses to the list?
EDIT: I have tried the ConcurrentBag and ConcurrentDictionary but without any improvement to the project as you can see in the comments…
Please before you post an answer read the comments bellow to make sure that you know what is going on.
EDIT: Tried to add a mutex to my code for addMsg and removeMsg but still crashing. If I use the mutex in the thread it will be slowed down.
I created a Timer in step of the thread and that solved the crashing problem. Here is the code if you want to see it.
private System.Windows.Forms.Timer myTimer = new System.Windows.Forms.Timer();
private void startThread() {
myTimer.Tick += new EventHandler(timerEvent);
myTimer.Interval = 30;
myTimer.Start();
}
private void timerEvent(object sender, EventArgs e) {
var it = labels.GetEnumerator();
while (it.MoveNext()) {
Label lb = it.Current;
// Label lb = labels.ElementAt(b);
if (lb.Location.X + lb.Width < 0) {
this.Controls.Remove(lb);
if (labels.Count > 1)
lb.Location = new Point(lastLb.Right, 0);
else
lb.Location = new Point(2000, 0);
lastLb = lb;
this.Controls.Add(lb);
this.Refresh();
}
if (leftLb != null)
if (leftLb.Location.X + leftLb.Width - lb.Location.X < -20)
lb.Location = new Point(leftLb.Right, 0);
else
lb.Location = new Point(lb.Location.X - 3, lb.Location.Y);
leftLb = lb;
}
}
The source of your problem is that while you are iterating over the list of labels You call either Remove or Add functions which modifies this list whis is not allowed while iterating over it. Instead of this
var it = labels.GetEnumerator();
while (it.MoveNext()) // the crash occurs here
I suggest something like that:
for(int i = 0; i < labels.Count; i++)
{
labels.remove(labels[i]); //this is valid of course the count of the list will change
//Here you can add or remove elements from the labels
}
Or you can try first to collect the removable items into a temporal list and later remove it from the original.
As others have already stated, the problem is you are modifying the collection while enumerating over it.
Now, the easiest workaround is obviously not to enumerate over the same collection that is being modified. And how do you do that? Simple, you just clone the collection, and iterate over it:
lock (labels)
{
var clone = new LinkedList<Label>(labels);
it = labels.GetEnumerator();
}
Now you can enumerate over it safely, without worrying about inconsistencies.
A few notes:
I am using a lock, because the cloning also must enumerate over your collection, and while it does it in a very short time, it is still required for synchronization. Off course, you need to uncomment the locks you've already added to addMsg and removeMsg.
The reason that locking your whole loop didn't work, is that when you call Invoke, you are essentially returning control to the thread that owns the object (the main GUI thread in this case). The problem is, that this thread is already stuck on handling whatever event caused addMsg or removeMsg to be called, leading to a deadlock.
You should also note that cloning a collection every 30 ms, isn't exactly efficient, and shouldn't be used in a production code, but given that this probably just an exercise, it should suffice. In real life, you should probably use a separate collection for the changes you are about to do (adding or removing labels), change this collection in addMsg and removeMsg, and then merge the changes to labels inside your thread, but outside of the iteration over the labels.
Not directly related to your question, but still: you should use a foreach loop instead of directly creating an enumerator object in C#.
As stated before, changing any collection while enumerating it, results in an exception in .Net. You can avoid this by using for or while loops.
However I don't see the point in using a Linked List in this scenario. It should be way simpler and more performant to use a ConcurrentDictionary and just add or remove the requested item. There is also a ObservableConcurrentDictionary available, although not part of the Framework. It is very stable, in my experience.
http://www.codeproject.com/Articles/208361/Concurrent-Observable-Collection-Dictionary-and-So

background worker thread

I am trying to figure out the best way to keep my application responsive. Below shows the code that I am currently working with. What i have found is that the Background worker thread is the way to go.
private void cleanFiles()
{
if (listView1.CheckedItems.Count != 0)
{
// If so, loop through all checked files and delete.
foreach (ListViewItem item in listView1.CheckedItems)
{
string fileName = item.Text;
string filePath = Path.Combine(tFile + fileName);
try
{
File.Delete(filePath);
}
catch (Exception)
{
//ignore files being in use
}
MessageBox.Show("Files Cleaned");
}
}
else
{
MessageBox.Show("Please put a check by the files you want to delete");
}
}
}
}
The easiest way to keep your program responsive is to use the BackgroundWorker.
List<string listWithFilenames = new List<string>();
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
bw.RunWorkerAsync(listWithFilenames);
See the documentation.
Have a look at these questions:
Cross-Threading issue with listView
How to execute code in the GUI Thread
assuming that the method you posted runs under the context of the UI thread,
all you need to do is wrap the logic (the foreach part)
in a method like :
private void DeleteFiles(object state)
{
/// your logic here
}
and call the ThreadPool.QueueWorkItem(new WaitCallback(DeleteFiles));
from the cleanfiles method.
if you run .NET 4.0, you can use something like:
Task myTask = Task.Factory.StartNew( () => DoWork(null));
then check the myTask status later , to see if its done.

Using BackgroundWorker to update the UI without freezes...?

I have the following code for population a ListView from a background thread (DoWork calls the PopulateThread method):
delegate void PopulateThreadCallBack(DoWorkEventArgs e);
private void PopulateThread(DoWorkEventArgs e)
{
if (this.InvokeRequired)
{
PopulateThreadCallBack d = new PopulateThreadCallBack(this.PopulateThread);
this.Invoke(d, new object[] { e });
}
else
{
// Ensure there is some data
if (this.DataCollection == null)
{
return;
}
this.Hide();
// Filter the collection based on the filters
List<ServiceCallEntity> resultCollection = this.ApplyFilter();
// Get the current Ids
List<Guid> previousIdList = this.GetUniqueIdList(listView);
List<Guid> usedIdList = new List<Guid>();
foreach (ServiceCallEntity record in resultCollection)
{
if (e.Cancel)
{
this.Show();
return;
}
else
{
// Get the top level entities
UserEntity userEntity = IvdSession.Instance.Collection.GetEngineerEntity(record.UserId);
AssetEntity assetEntity = IvdSession.Instance.Collection.GetAssetEntity(record.AssetId);
SiteEntity siteEntity = IvdSession.Instance.Collection.GetSiteEntity(record.SiteId);
FaultEntity faultEntity = IvdSession.Instance.Collection.GetFaultEntity(record.FaultId);
if (siteEntity == null || userEntity == null || faultEntity == null)
{
continue;
}
else
{
// Get the linked entities
RegionEntity regionEntity = IvdSession.Instance.Collection.GetRegionEntity(siteEntity.RegionId);
StatusEntity statusEntity = IvdSession.Instance.Collection.GetStatusEntity(record.ServiceCallStatus.StatusId);
ListViewItem item = new ListViewItem(siteEntity.SiteName);
item.SubItems.Add(siteEntity.Address);
item.Tag = record;
item.SubItems.Add(regionEntity.Description);
// Handle if an Asset is involved
if (record.AssetId > 0)
item.SubItems.Add(assetEntity.AssetDisplay);
else
item.SubItems.Add("N/A");
item.SubItems.Add(faultEntity.Description);
item.SubItems.Add(userEntity.UserDisplay);
item.SubItems.Add("TODO: Claimed By");
item.SubItems.Add(record.DateTimeStamp.ToString());
IvdColourHelper.SetListViewItemColour(item, false);
this.PopulateItem(item, ref usedIdList);
}
}
}
// Clean up the grid
this.CleanListView(previousIdList, usedIdList);
// Only autosize when allowed and when there are some items in the ListView
if (this.AllowAutoSize && listView.Items.Count > 0)
{
rsListView.AutoSizeColumns(listView);
this.AllowAutoSize = false;
}
this.Show();
}
}
Unfortunately, this causes the UI to freeze whilst in the foreach... is there any way to update/populate the ListView without it freezing the main UI?
A) You probably don't need to use this.Invoke and instead use this.BeginInvoke. Invoke blocks the current thread.
B) You don't need to define your own delegates you can use MethodInvoker
if(this.InvokeRequired) {
this.BeginInvoke(new MethodInvoker(() => PopulateThread(e)));
return;
}
It's much cleaner :)
You are using Control.Invoke to execute just about everything, meaning this code isn't multithreaded at all.
The proper way (involving a Backgroundworker) would be to use the UpdateProgress event to add elements. It is already synchronized.
But since you're hiding the control (or is it the Form ?) during this process you might as well build a List and on completion add it to the Listview. That piece of code shouldn't take long.
Or some sort of combination, adding small lists in an update event. And I wonder about the wisdom of Hide/Show, I expect this to just make the UI flicker. Leave them out or replace with SuspendLayout/Resumelayout.
Pump the events manually with
Application.DoEvents();

Categories