FIRST! I know very similar questions have been asked before, however, the answers have been so case specific that I seriously don't understand how to apply them. Please don't roast me.
All I want is to build a rather generic application that has CRUD modules and reporting (c# and MySQL), nothing special. However, everytime my app runs a line where there is a call to a method that queries the server the UI freezes a few ms, the freeze-like-stuttering of the UI feels incredibly annoying.
As I understood, every DB call needs to be done in a different thread with a background worker or a task so the main thread doesn't get busy and makes the UI unresponsive BUT whenever I try to do this I have issues because I can't access the UI components.
Can you guys help me with this simple login form as an example, please?
I have a simple form, 2 textboxes, txtUser and txtPassword and a button with the following code:
MySqlDataReader? user = MdlUsers.GetUserByUsername(txtUser.Text);
if (user != null && user.Read())
{
string? pwd = user["clave"].ToString();
if (pwd != null && pwd.Equals(txtPassword.Text))
{
MessageBox.Show("Acceso correcto.");
logged = true;
}
else
{
MessageBox.Show("Acceso denegado.");
logged = false;
}
}
else
{
MessageBox.Show("No regresó nada.");
logged = true;
}
This is the code to the MdlUser class:
public class MdlUsers
{
public static MySqlDataReader? GetUserByUsername(string username)
{
MySqlDataReader? result = null;
try
{
string qry = "SELECT * FROM usuarios WHERE login = #username";
MySqlCommand cmd = new MySqlCommand(qry, MdlConnection.Connect());
cmd.Parameters.AddWithValue("#username", username);
result = cmd.ExecuteReader();
return result;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
return null;
}
}
}
Whenever I click the button the UI stutters. So how can I handle this? Thank you very much.
The way to handle any situation of that sort is to perform your long blocking operation on a separate task, and when it finishes, post a call to the GUI thread.
You have not told us what kind of GUI you are using, (that's what tags are for on stackoverflow,) I suspect it is WinForms, so the way to do this in WinForms is with Control.BeginInvoke().
When you specify an Action to be invoked with Control.BeginInvoke(), that Action will run in the GUI thread, so it will be able to safely "access the UI components."
Related
I'm fairly new to databases and asynchronous programing. I'm making a POS app that will eventually have hundreds of customers and possibly thousands of transactions. When I want to do a customer search or look up a previous ticket I don't want my program to hang waiting on the results.
Here is the method that shows the search result:
private void searchCritiria_TextChanging(TextBox sender, TextBoxTextChangingEventArgs args)
{
FilteredCustomer.Clear();
if(searchCritiria.Text.Length >= 3)
{
SQLiteConnection dbConnection = new SQLiteConnection("Customers.db");
string sSQL = null;
sSQL = #"SELECT [first],[last],[spouse],[home],[work],[cell] FROM Customers";
ISQLiteStatement dbState = dbConnection.Prepare(sSQL);
while (dbState.Step() == SQLiteResult.ROW)
{
string sFirst = dbState["first"] as string;
string sLast = dbState["last"] as string;
string sSpouse = dbState["spouse"] as string;
string sHome = dbState["home"] as string;
string sWork = dbState["work"] as string;
string sCell = dbState["cell"] as string;
//Load into observable collection
if (searchType.SelectedIndex == 0)//name search
{
if(sFirst.Contains(searchCritiria.Text) || sLast.Contains(searchCritiria.Text) || sSpouse.Contains(searchCritiria.Text))
FilteredCustomer.Add(new Customer {first = sFirst, last = sLast, spouse = sSpouse, home = sHome, work = sWork, cell = sCell});
}
else//number search
{
if(sWork.Contains(searchCritiria.Text)|| sHome.Contains(searchCritiria.Text) || sCell.Contains(searchCritiria.Text))
FilteredCustomer.Add(new Customer { first = sFirst, last = sLast, spouse = sSpouse, home = sHome, work = sWork, cell = sCell });
}
}
}
}
Throughout my program I have void methods that are structured similar to this.
I'm not sure how to solve this issue. I tried doing some research but no success. Any advice would be greatly appreciated!
Edit
As correctly pointed out by #AndriyK, the answer would not have made your code asynchronous.
What it means:
Although the code works and you're able to push in data, if in case you reach a condition where two threads are trying to write into the database (Database is Locked condition, your application would still freeze until the database is not locked anymore or it times out. This means any loaders running would also freeze.
Making it asynchronous
To do so, you must run your SQLite code in a Task.Run() block. This would ensure your database runs on a different Thread and ensure your UI would never hang. This would also allow you to show ProgressRing (or other loaders) to the user while the application waits for the database to unlock. Below is the code:
public void PerformSQLTasks()
{
// your SQL code code here!
}
and run the above code by using:
Task.Run(() => PerformSQLTasks());
// or even
Task.Run(PerformSQLTasks);
Accepted Answer:
To make your UI not hang, you can make your method return a Task instead of void to make it awaitable. This will not make your code async but would allow the caller to await this method completion. To achieve this, follow the method signature below:
public Task PerformSQLTasks()
{
// your code here!
return Task.CompletedTask;
}
and you can call it by:
private async void searchCritiria_TextChanging(TextBox sender, TextBoxTextChangingEventArgs args)
{
await PerformSQLTasks();
}
Remember this won't make your code Asynchronous
I have thef ollowing background worker in my app which is meant to start a user's session automatically if there is not already one available.
This is done on a backgroundworker (backgroundInit) on initialisation. As you can see below, I have a while loop which continues to run as long as the var checker remains false:
var checker = false;
var i = 0;
while (checker == false)
{
_session = funcs.GetSession(_servers, _name);
_sessID = _session[0].Trim();
_servName = _session[1];
checker = funcs.CheckRunning("lync.exe");
i++;
if (i > 200)
{
break;
}
}
The CheckRunning method just checks if a specified program (in this case, "lync") is currently running and returns either true or false accordingly (This is done via a CMD command).
When I run the app in an empty session however, the while loop only iterates one time before breaking out, even though "Lync" is definitely not running.
Is there any reason why running a process or too many processes from within a Backgroundworker may cause it to exit?
As the comments mentioned, this was not an issue with the BackgroundWorker, but rather an exception occurring at _sessID = session[0].Trim(); where the session had not yet started, so there is no ID.
To resolve this, I simply placed a Try/Catch block around this assignment, and let the program silently ignore the exception:
try
{
_sessID = _session[0].Trim();
_servName = _session[1];
}
catch (Exception exp)
{
// MessageBox.Show(exp.Message);
}
This works for me, as the loop will continue checking until the counter i reaches the 200 limit, at which stage the program will accept failure.
I have a fairly simple WPF application that uses Entity Framework. The main page of the application has a list of records that I am getting from a database on startup.
Each record has a picture, so the operation can be a little slow when the wireless signal is poor. I'd like this (and many of my SQL operations) to perform in the background if possible. I have async/await setup and at first it seemed to be working exactly as I wanted but now I'm seeing that my application is becoming unresponsive when accessing the DB.
Eventually I'm thinking I'm going to load up the text in one query and the images in another background operation and load them as they come in. This way I get the important stuff right away and the pictures can come in in the background, but the way things are going it's still looking like it will lock up if I do this.
On top of that, I'm trying to implement something to handle connectivity issues (in case the wifi cuts out momentarily) so that the application notifies the user of a connection issue, automatically retries a few times, etc. I put a try catch for SQL exception which seems to be working for me, but the whole application locks up for about a minute while it is trying to connect to the DB.
I tried testing my async/await using await Task.Delay() and everything is very responsive as expected while awaiting the delay, but everything locks up when awaiting the .ToListAsync(). Is this normal and expected? My understanding of async/await is pretty limited.
My code is kind of messy (I'm new) but it does what I need it to do for the most part. I understand there's probably plenty of improvements I can make and better ways to do things, but one step at a time here. My main goal right now is to keep the application from crashing during database accessing exceptions and to keep the user notified of what the application is doing (searching, trying to access db, unable to reach DB and retrying, etc) as opposed to being frozen, which is what they're going to think when they see it being unresponsive for over a minute.
Some of my code:
In my main view model
DataHelper data = new DataHelper();
private async void GetQualityRegisterQueueAsync()
{
try
{
var task = data.GetQualityRegisterAsync();
IsSearching = true;
await task;
IsSearching = false;
QualityRegisterItems = new ObservableCollection<QualityRegisterQueue>(task.Result);
OrderQualityRegisterItems();
}
catch (M1Exception ex)
{
Debug.WriteLine(ex.Message);
Debug.WriteLine("QualityRegisterLogViewModel.GetQualityRegisterQueue() Operation Failed");
}
}
My Data Helper Class
public class DataHelper
{
private bool debugging = false;
private const int MAX_RETRY = 2;
private const double LONG_WAIT_SECONDS = 5;
private const double SHORT_WAIT_SECONDS = 0.5;
private static readonly TimeSpan longWait = TimeSpan.FromSeconds(LONG_WAIT_SECONDS);
private static readonly TimeSpan shortWait = TimeSpan.FromSeconds(SHORT_WAIT_SECONDS);
private enum RetryableSqlErrors
{
ServerNotFound = -1,
Timeout = -2,
NoLock = 1204,
Deadlock = 1205,
}
public async Task<List<QualityRegisterQueue>> GetQualityRegisterAsync()
{
if(debugging) await Task.Delay(5000);
var retryCount = 0;
using (M1Context m1 = new M1Context())
{
for (; ; )
{
try
{
return await (from a in m1.QualityRegisters
where (a.qanClosed == 0)
//orderby a.qanAssignedDate descending, a.qanOpenedDate
orderby a.qanAssignedDate.HasValue descending, a.qanAssignedDate, a.qanOpenedDate
select new QualityRegisterQueue
{
QualityRegisterID = a.qanQualityRegisterID,
JobID = a.qanJobID.Trim(),
JobAssemblyID = a.qanJobAssemblyID,
JobOperationID = a.qanJobOperationID,
PartID = a.qanPartID.Trim(),
PartRevisionID = a.qanPartRevisionID.Trim(),
PartShortDescription = a.qanPartShortDescription.Trim(),
OpenedByEmployeeID = a.qanOpenedByEmployeeID.Trim(),
OpenedByEmployeeName = a.OpenedEmployee.lmeEmployeeName.Trim(),
OpenedDate = a.qanOpenedDate,
PartImage = a.JobAssembly.ujmaPartImage,
AssignedDate = a.qanAssignedDate,
AssignedToEmployeeID = a.qanAssignedToEmployeeID.Trim(),
AssignedToEmployeeName = a.AssignedEmployee.lmeEmployeeName.Trim()
}).ToListAsync();
}
catch (SqlException ex)
{
Debug.WriteLine("SQL Exception number = " + ex.Number);
if (!Enum.IsDefined(typeof(RetryableSqlErrors), ex.Number))
throw new M1Exception(ex.Message, ex);
retryCount++;
if (retryCount > MAX_RETRY) throw new M1Exception(ex.Message, ex); ;
Debug.WriteLine("Retrying. Count = " + retryCount);
Thread.Sleep(ex.Number == (int)RetryableSqlErrors.Timeout ?
longWait : shortWait);
}
}
}
}
}
Edit: Mostly looking for general guidance here, though a specific example of what to do would be great. For these types of operations where I am downloading data, is it just a given that if I need the application to be responsive I need to be making multiple threads? Is that a common solution to this type of problem? Is this not something I should be expecting async/await to solve?
If you call this method from your UI thread, you will overload the capture of UI thread context and back on itself. Also, your service will not be necessarily "Performant" because it must wait until the UI thread is free before it can continue.
The solution is simple: just call the method passing the ConfigureAwait "false" parameter when you made the call.
.ToListAsync().ConfigureAwaiter(false);
I hope it helps
I made a code that create a Database in .sqlite, all working good but I want to be sure that when the user start for the first time the application the Database population must be completed. If the user abort the database population, the database must be deleted (because the application don't working with an incomplete resource). Now I've used the thread for execute the method that create this Database, and I've declared the thread variable global in the class, like:
Thread t = new Thread(() => Database.createDB());
The Database.createDB() method create the DB. All working perfect, the DB is created correctly. Now I fire the closing of the window that creating the DB like:
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
MessageBoxResult result = MessageBox.Show(
#"Sure?",
"Attention", MessageBoxButton.YesNo, MessageBoxImage.Question);
try
{
if (result == MessageBoxResult.Yes)
{
t.Abort();
if (File.Exists("Database.sqlite"))
{
File.Delete("SoccerForecast.sqlite");
Process.GetCurrentProcess().Kill();
} ....
The event was fired correct and the thread stopped, but when the condition start if (File.Exists("Database.sqlite")) the compiler tell me:
Can't delete file - in using by another process.
But I've stopped the thread, why this exception appear? What I doing wrong?
UPDATE:
In CreateDb() method I also have a call to other method of different class, one of this have the structure like this:
public void setSoccer()
{
Database.m_dbConnection.Open();
string requestUrl = "...";
string responseText = Parser.Request(requestUrl);
List<SoccerSeason.RootObject> obj = JsonConvert.DeserializeObject<List<SoccerSeason.RootObject>>(responseText);
foreach (var championships in obj)
{
string sql = "string content";
SQLiteCommand command = new SQLiteCommand(sql, Database.m_dbConnection);
try
{
command.ExecuteNonQuery();
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
string query = "select * from SoccerSeason";
SQLiteCommand input = new SQLiteCommand(query, Database.m_dbConnection);
SQLiteDataReader reader = input.ExecuteReader();
int i = 0;
while (reader.Read())
{
//reading data previously inserted in the database
}
Database.m_dbConnection.Close(); /
}
I was wondering where I should put the flag variable because this code have a different loop inside.
It could be that when you're aborting the thread it's not cleanly closing the database connections, hence the error you're seeing.
Might I suggest a slight redesign because using Thread.Abort is not ideal.
Instead use a variable as a cancel flag to notify the thread to shut down.
Then when the thread detects that this cancel flag is set it can properly close connections and handle the database delete itself.
Update:
A brief example to illustrate what I mean; it ain't pretty and it won't compile but it gives the general idea.
public class Database
{
public volatile bool Stop= false;
public void CreateDb()
{
if(!Stop)
{
// Create database
}
if(!Stop)
{
// Open database
// Do stuff with database
}
// blah blah ...
if(Stop)
{
// Close your connections
// Delete your database
}
}
}
...
protected override void OnClosing(CancelEventArgs e)
{
Database.Stop = true;
}
And now that you know roughly what you're looking for I heartily recommend Googling for posts on thread cancellation by people who know what they're talking about that can tell you how to do it right.
These might be reasonable starting points:
How to: Create and Terminate Threads
.NET 4.0+ actually has a CancellationToken object with this very purpose in mind Cancellation in Managed Threads
I have an order manager application created in C# and WPF. The order manager application needs to communicate back and forth with a shipping application that is written in a completely different shipping language. The method of communication between the programs is an XML file whose EnableShipping attribute is either a 0 or a 1 and a SQL database.
In my order manager application I have button that "Begins shipping" and changes the EnableShipping attribute from a 0 to a 1. The shipping application is looping and reads this value, begins shipping all of the orders whose certain attribute matches a string, changes this attribute to a different string, and marks a different attribute (Status Changed) to 1.
In my order manager application, as of now I have a thread that continually loops and checks the database for orders with a Status Changed attribute of 1, makes the changes to the UI and writes back to the database, Status Changed = 0.
Here is some code to show what my order manager application is doing in the thread.
while(true)
{
string enabled = null;
currentInstance = OrderManager.getCurrentInstance();
SqlCommand command = new SqlCommand("Select OrderNumber, BatchStatus from dbo.Orders where StatusChanged='1'", connection1);
SqlDataReader reader = command.ExecuteReader();
if (reader.HasRows)
{
while (reader.Read())
{
Int64 orderNum = (Int64)reader[0];
int index = linqForOrderIndex(orderNum);
string batchStatus = (string)reader["BatchStatus"];
SqlCommand statusChangedFalse = new SqlCommand("Update dbo.orders Set StatusChanged = '0' where OrderNumber = '" + orderNum + "'", connection2);
switch (batchStatus)
{
case "Untouched":
currentInstance.Orders[index].batchStatus = "Untouched";
break;
case "Batch Ready":
currentInstance.Orders[index].batchStatus = "Batch Ready";
break;
case "Shipped":
currentInstance.Orders[index].batchStatus = "Shipped";
break;
case "Error":
currentInstance.Orders[index].batchStatus = "Error";
break;
}
statusChangedFalse.ExecuteNonQuery();
Thread.Sleep(1000);
reader.Close();
reader = command.ExecuteReader();
}
currentInstance.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
new Action(
delegate()
{
currentInstance.refreshTreeFilters();
currentInstance.refreshOrderCounts();
currentInstance.batchReadyView();
}
));
}
}
So Even if you don't know exactly what is going on in the code, you know that I have to continuously check the database for status changed items, do work on those items in my collection as well as in the database, update the UI, and keep repeating the process.
At first I thought a good old thread would work like my code is doing now, but the UI becomes unresponsive when I am "doing work". I looked at some background worker code online seeing if maybe that would be a good choice for better UI responsiveness, but didn't know if this is a good solution as I need to continuously keep doing work and updating the UI.
Any thoughts or suggestions? appreciate it...
You could use BackGroundWorker with ReportsProgess to send the info to UI thread. You can pass an object. Also implement cancellation so you you don't shut down stream.
BackgroundWorker.ReportProgress Method
The other option is there is a way to get SQL notification.
Query Notifications in SQL Server