I am getting a ContextSwitchDeadlock when I try to add a large amount of graphics to a map. I would love to use the BackgroundWorker Class especially since I have never had the opportunity, but I am not sure how to in this situation.
C#:
private void AttributeSearchButton_Click(object sender, RoutedEventArgs e)
{
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.DoWork += new DoWorkEventHandler(RunQuery);
bw.ProgressChanged += new ProgressChangedEventHandler(ProgressChanged);
}
private void RunQuery(object sender, DoWorkEventArgs e)
{
_attributeQueryGraphicsLayer.Graphics.Clear();
QueryTask queryTask = new QueryTask("http://xx.x.x.xxx:6080/arcgis/rest/services/Calvert_City_Test/MapServer/2");
queryTask.ExecuteCompleted += GeneralQueryTask_ExecuteCompleted;
queryTask.Failed += GeneralQueryTask_Failed;
var dirty = DateTime.UtcNow;
Query query = new Query();
query.OutFields.Add("*");
query.ReturnGeometry = true;
query.Where = "CIS_METR LIKE '%" + QueryParametersTextbox.Text + "%' OR COMMENTS LIKE '%" + QueryParametersTextbox.Text + "%'";
query.OutSpatialReference = MyMap.SpatialReference;
queryTask.ExecuteAsync(query);
}
// Draw results when query is complete
private void GeneralQueryTask_ExecuteCompleted(object sender, ESRI.ArcGIS.Client.Tasks.QueryEventArgs args)
{
// Clear previous results
QueryDataGrid.Visibility = Visibility.Visible;
GraphicsLayer graphicsLayer = MyMap.Layers["QueryResults"] as GraphicsLayer;
graphicsLayer.ClearGraphics();
// Check for new results
FeatureSet featureSet = args.FeatureSet;
bool isExceedTransferLimit = args.ExceededTransferLimit;
try
{
if (featureSet.Features.Count > 0 && isExceedTransferLimit == false)
{
// Add results to map
foreach (Graphic resultFeature in featureSet.Features)
{
//resultFeature.Symbol = ResultsFillSymbol;
graphicsLayer.Graphics.Add(resultFeature);
}
}
else
{
MessageBox.Show("No features found");
}
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
}
// Notify when query fails
private void GeneralQueryTask_Failed(object sender, TaskFailedEventArgs args)
{
MessageBox.Show("Query failed: " + args.Error);
}
private void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.ProgressTextbox.Content = (e.ProgressPercentage.ToString() + "%");
}
I know that the logic is off in this, but I am clueless on how to fix it. I have referred to http://msdn.microsoft.com/en-us/library/System.ComponentModel.BackgroundWorker(v=vs.95).aspx for help with how to use the class, however the information provided is not for this specific situation.
What I am wanting to do:
When the user clicks the Search button, it will Run the query. This works perfectly. The problem is when it runs the GeneralQueryTask_ExecuteCompleted I get a ContextSwitchDeadlock message if there are a large amount of records. This also leaves the UI unresponsive.
From what I have read I would need to put:
if (worker.CancellationPending == true)
{
e.Cancel = true;
break;
}
where the time consuming task takes place. The problem is that there is no overloaded method for me to be able to do the QueryTask and the BackgroundWorker. Any help would be appreciated. If you need more information, please let me know.
Related
I read both BackgroundWorker still freezes UI and UI still freezes using backgroundWorker but neither have helped me solve my issue. I asked a question a few days ago about Datatable not displaying information after SQL query fill in backgroundworker, which I eventually solved by using Invoke and a delegate.
My current code (shown below) gives me the results I want, except for two things: it doesn't actually display the progress bar (at least that I can see) and it freezes the UI while running:
DataTable tab1table = new DataTable();
public Form1()
{
InitializeComponent();
Instantiatesearch1Thread();
}
private void Instantiatesearch1Thread()
{
search1Thread.WorkerReportsProgress = true;
search1Thread.WorkerSupportsCancellation = true;
search1Thread.ProgressChanged += search1Thread_ProgressChanged;
search1Thread.DoWork += search1Thread_Dowrk;
search1Thread.RunWorkerCompleted += search1Thread_RunWorkerCompleted;
}
private void sbutton1_Click(object sender, EventArgs e)
{
search1Thread.RunWorkerAsync();
}
void search1Thread_Dowrk(object sender, DoWorkEventArgs e)
{
int percentprogress = 0;
var worker = sender as BackgroundWorker;
// Ensure process is thread-safe by using Invoke method
Invoke(new MethodInvoker(delegate
{
// Search1 button event handler
using (SqlConnection conn = new SqlConnection(Connection_Info))
{
conn.Open();
using (SqlDataAdapter cmd = new SqlDataAdapter(comboBox1SQL, conn))
{
//MessageBox.Show("Working");
if (comboBox1.Text.Contains("ID"))
{
long para = long.Parse(search1.Text);
cmd.SelectCommand.Parameters.Add(new SqlParameter
{
ParameterName = "#combo1Par",
Value = para,
SqlDbType = SqlDbType.BigInt
});
}
// Clear datatable if it contains any information and then fill it
if (tab1table != null)
tab1table.Clear();
cmd.Fill(tab1table);
// Set Progress Bar info
search1Progress.Maximum = tab1table.Rows.Count;
groupBox2.Controls.Add(search1Progress);
search1Progress.BringToFront();
}
}
// This is a long calculation where I am reporting the
// progress every iteration (which is probably not helping,
// but didn't change anything when commented out).
if(!String.IsNullOrEmpty(search1.Text) && tab1Combo1.SelectedIndex > -1 || tab1Combo2.SelectedIndex > -1)
{
for (int i = tab1table.Rows.Count - 1; i >= 0; i--)
{
DataRow dr = tab1table.Rows[i];
if (!dr.ItemArray.Contains(search1.Text.ToString()))
{
dr.Delete();
tab1table.AcceptChanges();
}
percentprogress++;
worker.ReportProgress(percentprogress);
}
}
}));
}
void search1Thread_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
search1Progress.Value = e.ProgressPercentage;
}
void search1Thread_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
tab1datatable.DataSource = null;
tab1datatable.DataSource = tab1table;
tab1datatable.Update();
groupBox2.Controls.Remove(search1Progress);
MessageBox.Show(" Your search returned " + tab1table.Rows.Count.ToString() + " results");
}
Can anyone point out what I am doing wrong here?
My issue may have something to do with Invoke as I know it's not the best method with BackgroundWorker, but I couldn't get it to update the UI using any other method.
first off I'd like to say I'm brand new to C# so I am not too aware with how the background worker is supposed to be implemented. I have a GUI program that basically pings a domain a returns the response to a textbox. I am able to get it to work normally, however, it freezes the code because it is running on the same thread which is why I am trying to implement a background worker.
Here is the basic setup
private void button1_Click(object sender, EventArgs e)
{
url = textBox1.Text;
button1.Enabled = false;
button2.Enabled = true;
bgWorker.DoWork += new DoWorkEventHandler(bgWorker_DoWork);
bgWorker.RunWorkerAsync();
}
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
do
{
if (bgWorker.CancellationPending)
break;
Invoke((MethodInvoker)delegate { monitor(); });
} while (true);
}
public void monitor()
{
textBox2.AppendText("Status of: " + url + "\n");
Status(url);
System.Threading.Thread.Sleep(30000);
}
private void Status(string url)
{
// This method does all the ping work and also appends the status to the Text box as it goes through , as OK or down
}
I have not worked with bgworkers before and as you can imagine it's confusing. I've looked at tons of other articles and I can't seem to get it. Sorry if the code looks crazy, I'm trying to learn.
Use Microsoft's Reactive Framework (NuGet "System.Reactive.Windows.Forms" and add using System.Reactive.Linq;) and then you can do this:
private void button1_Click(object sender, EventArgs e)
{
var url = textBox1.Text;
Observable
.Interval(TimeSpan.FromMinutes(0.5))
.SelectMany(_ => Observable.Start(() => Status(url)))
.ObserveOn(this)
.Subscribe(status => textBox2.AppendText("Status of: " + status + "\n"));
}
You then just need to change Status to have this signature: string Status(string url).
That's it. No background worker. No invoking. And Status is nicely run on a background thread.
You've got several mistakes. First,
Invoke((MethodInvoker)delegate
{
monitor();
});
will call monitor() on your UI thread. In almost all cases you should not call methods on other threads. You especially should not call methods that block or do anything that takes more than a few milliseconds on your UI thread, and that is what this does:
System.Threading.Thread.Sleep(30000);
Instead of calling a method on another thread; submit immutable data to the other thread and let the thread decide when to handle it. There is an event already built in to BackgroundWorker which does that. Before you call bgWorker.RunWorkerAsync() do this:
url = new Uri(something);
bgWorker.WorkerReportsProgress = true;
bgWorker.WorkerSupportsCancellation = true;
bgWorker.ProgressChanged += Bgw_ProgressChanged;
private void Bgw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
textBox2.AppendText("Status of: " + url + ": " + e.UserState.ToString()
+ Environment.NewLine);
}
Your bgWorker_DoWork should look more like this:
void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
while (!bgw.CancellationPending)
{
System.Threading.Thread.Sleep(new TimeSpan(0, 0, 30));
var status = ResultOfPing(e.Argument as Uri);
bgw.ReportProgress(0, status);
}
e.Cancel = true;
}
and you should call it like this:
bgWorker.RunWorkerAsync(url);
You've got a second problem. BackgroundWorker creates a thread, and your thread is going to spend most of its time blocked on a timer or waiting for network responses. That is a poor use of a thread. You would be better off using completion callbacks or async/await.
The background worker is running on a thread pool thread, but your call to Status and Sleep is running on the UI thread. You need to move that stuff back into bgWorker_DoWork.
Try this code:
public partial class Form1 : Form
{
bool cancel;
public Form1()
{
InitializeComponent();
}
public void StartPinging()
{
this.cancel = false;
startButton.Enabled = false;
stopButton.Enabled = true;
responseBox.Clear();
responseBox.AppendText("Starting to ping server.");
responseBox.AppendText(Environment.NewLine);
var bw = new BackgroundWorker
{
WorkerReportsProgress = false,
WorkerSupportsCancellation = true
};
bw.DoWork += (obj, ev) =>
{
while (!cancel)
{
// Ping Server Here
string response = Server.PingServer();
this.Invoke(new UiMethod(() =>
{
responseBox.AppendText(response);
responseBox.AppendText(Environment.NewLine);
}));
}
};
bw.RunWorkerCompleted += (obj, ev) =>
{
this.Invoke(new UiMethod(() =>
{
responseBox.AppendText("Stopped pinging the server.");
responseBox.AppendText(Environment.NewLine);
startButton.Enabled = true;
stopButton.Enabled = false;
}));
};
bw.RunWorkerAsync();
}
delegate void UiMethod();
private void startButton_Click(object sender, EventArgs e)
{
StartPinging();
}
private void stopButton_Click(object sender, EventArgs e)
{
responseBox.AppendText("Cancelation Pressed.");
responseBox.AppendText(Environment.NewLine);
cancel = true;
}
}
public class Server
{
static Random rng = new Random();
public static string PingServer()
{
int time = 1200 + rng.Next(2400);
Thread.Sleep(time);
return $"{time} ms";
}
}
Erwin, when dealing with C# - threads and UI elements usually you will come across cross-thread operations i.e. Background thread with UI threads. This interaction needs to be done in thread safe way with the help of Invoke to avoid invalid operations.
Please look into below resource: InvokeRequired section.
https://learn.microsoft.com/en-us/dotnet/framework/winforms/controls/how-to-make-thread-safe-calls-to-windows-forms-controls
I have a problem with background worker, it gets called twice thus, increasing the time of execution for my long routine, I created background worker manually so, there is no chance for the DoWork to be initialized within the initializeComponent() method, any help is appreciated.
here is my code:
// constructor
public TeacherScheduleForm(Therapist therapist)
{
this.therapist = therapist;
InitializeComponent();
bw = new BackgroundWorker();
bw.WorkerSupportsCancellation = true;
bw.WorkerReportsProgress = true;
bw.DoWork += bw_DoWork;
bw.ProgressChanged += bw_ProgressChanged;
bw.RunWorkerCompleted += bw_RunWorkerCompleted;
load = new LoadingForm();
}
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
load.AppendProgress(e.ProgressPercentage);
// load.AppendText(e.ProgressPercentage.ToString() + "%");
Console.Write("Progress: " + e.ProgressPercentage);
// MessageBox.Show("Progress : " + e.ProgressPercentage);
}
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if ((e.Cancelled == true))
{
MessageBox.Show("Cancelled");
}
else if (!(e.Error == null))
{
MessageBox.Show("Error : " + e.Error);
}
else
{
updateUI();
load.Close();
Console.Write( "Done!");
}
}
// do work of background worker
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 1; (i <= 2); i++)
{
if ((worker.CancellationPending == true))
{
e.Cancel = true;
break;
}
else
{
// Perform a time consuming operation and report progress.
Console.Write("Before Doing work");
setup(therapist.therapistID + "", schoolYear);// the time consuming operation
Console.Write("Doing work");
//System.Threading.Thread.Sleep(100);
worker.ReportProgress((i*5));
}
}
}
The background worker is called when the user selects the school year through a combo box which is in this code below:
private void comboBoxSchoolYear_SelectedIndexChanged(object sender, EventArgs e)
{
//load = new LoadingForm();
schoolYear = int.Parse(comboBoxSchoolYear.SelectedValue + "");
try{
if (!bw.IsBusy)
{
bw.RunWorkerAsync();
load.ShowDialog();
}
else
{
bw.CancelAsync();
}
}
catch(Exception ex)
{
Console.Write("Error : " + ex.Message);
}
}
You are loading the form after creating the event-handler. Thats the only point I can think off doing the trouble. Try to load the form first and then create the handler.
Reason: At InitializeComponent(); the IndexChanged normally will fire up because the control is set at this point with its index. I havnt noticed this behaviour on FormLoad till now. But as I cant see any other problem in here its worth a try.
IF this doesnt solves it, you should also take care if TeacherScheduleForm is being called twice.
Something handy for debugging-purposes:
MessageBox.Show((new StackTrace().GetFrame(0).GetMethod().Name));
Paste this into your event/method or whatever. It will popup a messagebox with the method-name which called your current method. In this case (from comments) it would've raised 2 messageBoxes saying TeacherScheduleForm for both.
I've saved this to my code-snippets.
I'm doing work on some database code. At some point I do need to move these calls off of the UI thread and on to a Background thread. I've got my code posted below but I'm curious as to some examples for doing this or if someone could show how to do this. I've done Async Calls in Java just trying to wrap my head around doing this C# with Visual Studio 2013. Any help would be appreciated.
Database Code :
static public Project.Project QueryProject(string projDatabaseName)
{
Project.Project proj = new Project.Project();
string connStr = "server=localhost;database=" + projDatabaseName + ";user=******;port=3306;password=*****;";
string queryStr = "SELECT * FROM " + projDatabaseName + ".project";
MySqlConnection myConnection = new MySqlConnection(connStr);
MySqlCommand myCommand = new MySqlCommand(queryStr, myConnection);
myConnection.Open();
try
{
MySqlDataReader myReader = myCommand.ExecuteReader();
while (myReader.Read())
{
proj.ProjectID = int.Parse(myReader["ProjectID"].ToString());
proj.ProjectName = myReader["ProjectName"].ToString();
proj.ProjectStartDate = Convert.ToDateTime(myReader["ProjectStartDate"]);
proj.ProjectEndDate = Convert.ToDateTime(myReader["ProjectEndDate"]);
proj.ProjectNotes = myReader["ProjectNotes"].ToString();
}
myReader.Close();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
return null;
}
finally
{
myConnection.Close();
}
return proj;
}
Call to database code:
savedProj = ProjectDbInteraction.QueryProject(currentProjDb);
You should do some research into BackgroundWorker https://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker%28v=vs.110%29.aspx and Threading in general with C#.
I would try using a background worker that would look something like this:
//setting up your background worker.
var worker = new BackgroundWorker();
worker.DoWork += bgw_DoWork;
worker.RunWorkerCompleted += bgw_WorkCompleted;
worker.ProgressChanged += bgw_ProgressChanged;
private void bgw_DoWork(object sender, DoWorkEventArgs e)
{
e.Result = ProjectDbInteraction.QueryProject(currentProjDb);
}
private void bgw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
//Here you can inspect the worker and update UI if needed.
}
private void bgw_WorkCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//check for errors, and other unexpected results first...
//assuming that savedProj is some private member variable of your class,
//just assign the variable to the Result here.
savedProj = (Project.Project)e.Result;
}
i got some really simple code, but cant get it to work. I'm using BackgroundWorker. Problem is that RunWorkerCompleted is fired way to fast. Instantly after running i get message "Work completed", but application remains frozen for couple of seconds as 'DataType data = new DataType(path);' is beign executed. After that i got all my DataGridViews etc filled correctly. If i swap this single line with Thread.Sleep everything seems to work well. Any ideas?
public frmWindow(string path)
{
InitializeComponent();
DataType d;
backgroundWorker1.RunWorkerAsync(path);
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
string path = e.Argument as string;
DataType data = new DataType(path);
e.Result = data;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
d = e.Result as DataType;
MessageBox.Show("Work completed");
}
How about you use Debug.Write instead of MessageBox.Show with timers to show when the Methods are entered and exited.
While it is possible for this same background thread to act on your UI, its almost always NOT a good thing to do--UI is not threadsafe.
BackgroundWorker backGroundWorker1;
public frmWindow(string path)
{
InitializeComponent();
DataType d;
backGroundWorker1 = new BackgroundWorker();
backGroundWorker1.DoWork += (s, e) =>
{
System.Diagnostics.Debug.Write("Work started at: " + DateTime.Now + Environment.NewLine);
string path = e.Argument as string;
DataType data = new DataType(path);
e.Result = data;
};
backGroundWorker1.RunWorkerCompleted += (s, e) =>
{
d = e.Result as DataType;
System.Diagnostics.Debug.Write("Work completed at: " + DateTime.Now + Environment.NewLine);
};
backGroundWorker1.RunWorkerAsync();
}