Winforms asynchronous loading large data? - c#

I just received a bug list for an old app developed yeah years ago and one of the things i need to sort out is the amount of time it takes to load data into one screen,of course, while the screen is frozen and unfortunately this is in WinForms .NET 4.5. The data is loaded into a WinForms DataGridView. I would like to find out if there is any way of loading this data using C# 5 async and await,while refreshing the grid to add the next set of data. It may be while scrolling or in the background.Any ideas?

Try loading all of the data into an array from an asynchronous thread and then using Invoke to insert the array into the DataGridView.
Call this from Form_Load
new Thread(new ThreadStart(Run)).Start();
then create this method
private void Run()
{
//DataArray
//Load Everything into the DataArray
Invoke(new EventHandler(delegate(object sender, EventArgs e)
{
//Load DataArray into DataGridView
}), new object[2] { this, null });
}
This I believe is the most optimized way to load something into a Control since Controls are not allowed to be touched outside of the MainThread. I don't know why Microsoft enforces this but they do. There may be a way to modify Controls outside of the MainThread using Reflection.
You could additionally slowly load the data into DataGridView. It will take longer to load all of the data but it will allow you to continue to use the Form while it is loading.
private void Run()
{
//DataArray
//Load Everything into the DataArray
for(/*Everything in the DataArray*/)
{
Invoke(new EventHandler(delegate(object sender, EventArgs e)
{
//Load 1 item from DataArray into DataGridView
}), new object[2] { this, null });
Thread.Sleep(1); //This number may have to be tweeked
}
}

You want to use virtual mode. Other solutions do all the upfront work to load the data and then put it into the grid (which still gives you a startup delay), or else they add chunks of data at a time (which messes up your scrolling).
Virtual mode reverses this; instead of you throwing your data at the grid, virtual mode will have the grid request your data.

Related

DataGridView InvalidOperationException reentrant call to SetCurrentCellAddressCore

I've been working on this for about 8 months. It's been an annoyance more than anything until recently when I moved from a DataSet / DataTables to lists. Now the problem is a lot more prevalent (I think because the lists appear to be a LOT more efficient).
This question has been asked a few times but none of them really hit on what truly is going on (nor are any of them answered). The odd thing is I can't isolate where in my code this is causing the exception as the debugger pulls up the program.cs which only has this code:
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MyApp());
}
The application exception is on the Application.Run... line.
I'm using the DataGridView as a back ground processing log display. I have numerous background processes in a service that communicate back up to a winform app. The form listens for these messages (event handler / signalr) and also remove messages (like a FIFO queue) to not exceed a maximum defined amount in a list then I process the message and sort them into a BindingList[]. Then in the datagrid if I click on an item, it will display the full message in a textbox. I have multiselect turned off and again, the datagridview is read only.
Oh also, the BindingList[] is bound / rebound to the datagrid from another control so I can select which list to display in the datagridview. This is not the issue as I've isloated the issue by forcing a single specific list in the code and still have the problem.
To get this to crash, I can click on datagrid numerous times and eventually it will crash. If I really want to crash it quickly, I click on the datagridview and scroll (keyboard arrow) down and up and I can crash it in a few seconds.
I found this article (click here) on StackOverflow that describes what is going on. And in one of the comments it refers to a Microsoft Bug Report(click here) which stats this is by design! However, most are talking about manipulating the cells which I am not doing. The top message is nearly identical to what is happening to me but the programmer is using an inherited DataGridView so his solution will not work for me.
This does have to do with adding and or deleting items from the BindingList. I can get it to crash if I have either of the going on while scrolling / selecting in the DataGridView. But that code is very simple:
private void DelRow( string szTableName)
{
try
{
int nProcQueue = qdList.Queue(szTableName);
MsgQueues[nProcQueue].RemoveAt(0);
this.BeginInvoke(new MethodInvoker(Refresh_dgvDetail));
}
catch (Exception ex)
{
LogEx(ex);
}
}
and
private void AddRow(LogObject oLogObject, string szTableName)
{
try
{
int nQueueNumber = qdList.Queue(szTableName); // helper object to return queue number based off the name of the list
MsgQueues[nQueueNumber].Add(oLogObject);
this.BeginInvoke(new MethodInvoker(Refresh_dgvDetail));
}
catch (Exception ex)
{
LogEx(ex);
}
}
This really seems like a c# bug... Why MS would have this as designed is beyond me...?
Anyone know how to stop this behavior?
Grek40, you're right; I was wrong. I did the MethodInvoker for just the add; not the delete. It had to be done for both. Basically anything method that touches the datagridview needs to have MethodInvoker. This is an example of what I did:
this.Invoke((MethodInvoker)delegate { MsgQueues[nCurrentQueue].RemoveAt(0); });
Problem went away.
Set the column on the side to ReadOnly = true;

C# Trouble with event handlers on dieing threads

First of all my Main is STAThread and i am not able to change this without facing problems with the rest of my code.
So, I am currently using Rapi2 To pull and push files between my Pda and Computer. Now since there is quite a bit of number crunching i would like to do this on a separate thread. First wat i do is create an RemoteDeviceManager and then make an Event Handler for when a device connects.
public void Initialize()
{
_deviceManager = new RemoteDeviceManager();
_deviceManager.DeviceConnected += DeviceConnected;
}
As you can see when my device connects it triggers DeviceConnected.
This is the class that i end up pulling and pushing a database and do some number work.
private void DeviceConnected(object sender, RemoteDeviceConnectEventArgs e)
{
if (e.Device == null) return;
... (unimportant code)
}
Now the problem here is that i would want to run the code inside DeviceConnected in a new thread but i am unable to access e inside the new thread since it was initialized outside that thread
So now wat i tried was make a new thread before calling Initialize.
public Watcher()
{
_dataThread = new Thread(Initialize);
_dataThread.IsBackground = true;
_dataThread.Name = "Data Thread";
_dataThread.SetApartmentState(ApartmentState.MTA);
_dataThread.Start();
}
But the thread dies and thus never fires my event handler.
I tried many different ways to make it work or keep my thread alive but without any success. I hope someone here is able to give me some hints.

Idea for Timer with thread C# Window Form

My project have a MainForm, i show F_Insert and set MdiParent for MainForm
F_Insert f = new F_Insert();
f.MdiParent = this;
f.Show();
In F_Insert, i put a button with CLick event like this
private void btn_Add_Click(object sender, EventArgs e)
{
//Insert data to SQL
}
Besides, i want to auto upload data that inserted from F_Insert every 5 second
I use System.Timer.Timer and set it to Thread in MainForm_Load
Thread t1 = new Thread(new ThreadStart(Timerss)); //In MainFormLoad event
t1.Start();
public void Timerss()
{
System.Timers.Timer timer = new System.Timers.Timer(5000);
timer.Elapsed += Timer_Insert_Tick;
timer.AutoReset = true;
timer.Start();
}
private static void Timer_Insert_Tick(object sender, System.Timers.ElapsedEventArgs e)
{
//code auto upload data to server here
//Data get from Sql Local to upload SQL in Server
}
The problem is it's not working good. I feel when i insert data form F_Insert, data is affected by Timerss thread that i start in MainForm load.
The simple way to show you my problem: when i split two work (Insert
and upload) into 2 difference work, it working good, it's mean i'm
insert data complete and then, i upload data, it will working good.
But when i insert data and data auto upload by timer in the same time,
i see some error that: conection sql close or open error, no data get
from F_Insert, sometime it get duplicate data (old data)
Please suggeted me some idea for this problem. Sorry but i'm newbie in thread. Thank you !!!
Well depending on what you trying to do this code should be modified but i hope it'll give you starting point to work with.
First of all let's create static field:
static volatile bool isDataChanged;
Keyword volatile makes this bool thread-safe, it means that this field always holds latest (and therefore correct) value when it is accessed by any thread in multi-thread environment).
We need this field to hold bool value that is used later to check whether the data was modified or not.
Assuming that data is modified inside click event handler, we should set this flag to true :
private void btn_Add_Click(object sender, EventArgs e)
{
// Data is modified in UI thread
isDataChanged = true;
}
Then let's assume that in Timer tick event we should upload the latest data to database (data is located in UI thread and could change in time span in between two tick events).
First of all we check if there is any changes to our data and if there's not we just exits the method. If changes was done we need to upload them to DB and in order to do so we have to deal with the fact that data in Timer thread could very well not be the same as data in our UI thread.
Let's create local variable that will hold correct data that we fetch from UI thread and use this.Invoke() to invoke Func<object> delegate on UI thread. The method that is attached to delegate returns instance of correct data retrieved from UI thread as object. We cast it explicitly to the type that our data is (usually it's one of collection types like List<T> or Dictionary<T1, T2>) and use this data to upload it to the DB.
After that, because our data in DB is the correct one, we change flag isDataChanged to false.
private void Timer_Insert_Tick(object sender, System.Timers.ElapsedEventArgs e)
{
if(!isDataChanged) return;
// A very important line. It gets data from UI thread before uploading it
// Change DataType with your data Type and dataToUpload with data instance
DataType data = (DataType)this.Invoke(new Func<object>(() => dataToUpload));
//use data to upload your data to server
isDataChanged = false;
}
P.S.
Also it is better to place reference to our Timer in outer scope (so it can be accessed from anywhere inside the form)
public partial class MyForm : Form
{
...
System.Timers.Timer timer;
public void Timerss()
{
timer = new System.Timers.Timer(5000);
}
...
}

Application.Wait or Thread.Sleep

I am already using backgroundworker.RunAsyn() to run my code on a separate thread. However I am hitting a portion where the code iterates to the next line before the previous line is completed. Should I create a separate backgroundworker to handle that? Or should I use Application.Wait() or Thread.Sleep() I am not sure the amount of time to delay and I'd rather not have my program just sitting around waiting for extra un-needed time so I am not sure which route to take. Here is a snippet of the trouble-maker.
public Form_Main()
{
InitializeComponent();
backgroundworker1.WorkerReportsProgress = true;
backgroundworker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
backgroundWorker1_ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
}
private void btnOpenRefreshSave_Click()
{
backgroundWorker1_RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
Excel.Application exApp;
Excel._Workbook exBook;
Excel._Worksheet exSheet;
exBook = (Excel._Workbook)(exApp.WOrkbooks.Open("C:\\Book1.xlsx"));
exSheet = (Excel._Worksheet)(exBook.ActiveSheet);
//This is the line of code that often times takes a while
exBook.RefreshAll();
//end of trouble line
exBook.SaveAs("C:\\Updated_Book1.xlsx");
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
}
A few things come to mind on what to do here. You could try using something similar to the below
if (Application.CalculationState === xlDone Then
everything is finished calculating[enter link description here][1]
Another option would be (as others have suggested) changing the background refresh property. A quick scan of the workbooks could programmatically change that for you
foreach (Wrksheet ws in workbook.wss)
{
foreach (QueryTable table in ws.QueryTables)
table.BackgroundQuery = false;
}
workbook.RefreshAll();
The problem is caused because RefreshAll is running on a background thread. So basically you have your own backgroundworker running and another one you did not anticipate for.
The documentation for refreshAll says :
Objects that have the BackgroundQuery property set to true are refreshed in the background.
So you can get out of this problem only be setting that property to false. Then the refreshall would run in the context of your backgroundworker which is what your intent is.
If this still does not work, then you have to rethink your logic and look for an event of some kind that is triggered when the refresh is done. If this does not exist, then there is no solution other than a sleep, but that is not a good solution at all because you don't know how long to sleep.
Why do you want to delay something, can't you do saving your workbook on one of its events like SheetCalculate (Occurs after any worksheet is recalculated or after any changed data is plotted on a chart) and setting some flag in your code and reset that on that event (or any more relevant event)

How would I load photos in the background in a wpf desktop app so that it doesn't take a few seconds to load the next photo in the gallery?

Currently my program reads images and text in a record from an xml file, displays them on the screen, and then the click of the previous/next buttons moves to the next record. However, it seems to need a few seconds loading time between each photo and I'd like it to be instant, like how Windows Photo Gallery would...or Facebook photos (bear in mind this is not a web app).
I searched found a few similar situations to mine but none seemed to fit my situation. I tried making a class, based on my search, to deal with background loading and calling it in my program, but it's fraught with error and probably won't even do what I want it do:
//ImageManager.cs
class ImageManager
{
private Dictionary<string, Image> images = new Dictionary<string, Image>();
public Image get(string s)
{ // blocking call, returns the image
return load(s);
}
private Image load(string s)
{ // internal, thread-safe helper
lock (images)
{
if (!images.ContainsKey(s))
{
Image img = images.Add(s, img); //load the image s - ERROR cannot implicitly convert type void to image. Void??
return img;
}
return images[s];
}
}
public void preload(params string[] imgs)
{ // non-blocking preloading call
foreach (string img in imgs)
{
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += (s, e) => { load(img); }; // discard the actual image return
bw.RunWorkerAsync();
}
}
}
//MainWindow.cs
ImageManager im = new ImageManager();
im.preload("Data/Images"); // Errors - im is a field but used like a type/token '('
Many thanks in advance
Your ImageManager should work with ImageSources, not Images. Even if you get your current code to work you'll find that your UI still hangs because you have no choice but to perform the work on the UI thread. If you instead deal with ImageSources, you can load them on a background thread and then freeze them in order to use them from the UI thread. This frees you to pre-emptively load images, or to show a loading animation whilst they load.
BitmapFrame.Create is likely the method you want to be using to load the images.
Consider caching scaled down images - 1:1 of what you want to show, or even smaller. This way loading of preview will be much faster and if user looks at the image long enough you can load full image.
With modern photos original size of the image is usually way bigger than can be normally diaplayed. So if you always read original images you spend large amount of disk IO on something that will never be shown.
Usual note: it may not be case in your program. As with any performance issues measure, than optimize.

Categories