In my application I display data from a online web service into several UITableViews. I have added several ways for the user to update the data, but the TableView.ReloadData() method does not seem to work.
When the user calls for an update, I get a new set of data from the server, pass it to the UITableViewSource instance that is attached to the UITableViewController and then call the ReloadData() method, which unfortunately does not in fact reload the data. Only after I return to the main screen and then go back to the table view (because it is already created, I just display the instance that already exists) does the new data show up in the tableview.
What am I doing wrong? I tried creating a new instance of the UITableViewSource when updating the data, but that does not help either.
Here is the code for loading data into the tableview (I reuse it for any event that requires data to be loaded into it):
dataControl.GetList(Tables.UPDATES)); //gets data from the server and passes it to the SQL database
Source source = GetSource(theType.Name, theType, groups); //creates a new source loaded with the data
Updates.TableView.Source = source;
Updates.TableView.AllowsSelection = false;
Updates.TableView.ReloadData();
This code is of course executed in a separate thread that invokes on the main thread.
Basically the same code is called when the user asks for an update(an animation is played while the background thread works).
Pavel is correct - try the following to see if it works:
InvokeOnMainThread(delegate{
Updates.TableView.Source = source;
Updates.TableView.AllowsSelection = false;
Updates.TableView.ReloadData();
});
In future, whenever you're dealing with something that will change the UI currently shown, you will need to ensure that it takes place on the main thread (also known as the GUI thread). This InvokeOnMainThread is a method from NSObject so can be called like above in all UIViews / UIViewControllers etc - you can also call it from an entirely C# class using:
MonoTouch.UIKit.UIApplication.SharedApplication.InvokeOnMainThread(delegate{ /*code here*/ });
You say it is done in a different thread, could it be that the worker thread cannot call the UI thread? I.e. you should call the ReloadData via delegate to be sure it gets called in the UI thread and not "only" in the worker thread, as it might interlock and never get actually called (happened to me in a different scenario).
I also ran into this problem and found this question. Using some of the hint here, I finally got it work by reseting the DataSource and call ReloadView().
tableView.DataSource = new MyDataSource(...);
tableView.RelaodData();
From my testing, it doesn't make different if I wrap the ReloadView() within the InvokeOnMainThread or not. Well, that's maybe because I'm not using worker thread.
It is strange that in another case I could refresh the table view by simply calling ReloadData() in ViewDidAppear(). The only difference is that the above case is the refresh within the same view.
You can reload datain viewwillappear() or set load data code in viewwillappear().
For googlers:
I had the same issues. This is what fixed it for me
InvokeOnMainThread(delegate {
myTableView.Source = new TableViewSource();
myTableView.ReloadData();
this.View.SetNeedsDisplay();
});
Related
I am working on a MVC 5 based report generating web application (Excel files). On one "GenerateReports" page, on button click, I am calling StartMonthly function. This takes control to a void method "GenerateReportMainMonthly" in the controller. This method calls another void method "GenerateReportMain". In GenerateReportMain, there are 5 other void functions that are being called.
I do not want the control to get stuck at back-end until the report generation is completed. On button click, an alert box should show "Report Generation started." and the control should come back to the "GenerateReports" page.
I have tried ajax but have not been able to get the control back to UI. How can I get the control back to the same page without waiting for the back-end process to complete?
$('#btnStart').on('click', StartMonthly);
function StartMonthly() {
var url = '/GenerateReport/GenerateReportMainMonthly';
window.location.href = url;
}
public void GenerateReportMainMonthly()
{
_isDaily = false;
GenerateReportMain();
}
It seems you are after running background tasks in your controllers. This is generally a bad idea (see this SO answer) as you might find that your server process has been killed mid-way and your client will have to handle it somehow.
If you absolutely must run long-ish processes in your controller and cannot extract it into a background worker of some sort, you can opt for something like this SO answer suggests. Implementation will vary depending on your setup and how fancy you are willing/able to go, but the basics will ramain the same:
you make an instantaneous call to initiate your long action and return back a job id to refer back to
your backend will process the task and update the status accordingly
your client will periodically check for status and do your desired behaviour when the job is reported complete.
If I were to tackle this problem I'd try to avoid periodic polling and rather opt for SignalR updates as describled in this blog post (this is not mine, I just googled it up for example).
I'm using a timer to regularly read-in a log file and post certain contents to a textbox in a Windows Form Application I'm developing in C#. I do this by sending a string to set_textbox_thread which posts the text (s) to the appropriate textbox (tbc) in the else below. The code below works in the practice application I built. However, the same code runs, but fails to update my textbox in the full application I'm building. It seems to be failing on the Invoke statement, which fails to call set_textbox_thread again. My theory is that, because my full application has a more complex set of controls, I am not calling Invoke via the correct control. I've tried calling it via "this" the parent panel, the parent form, and the button that triggers set_textbox_thread, and am dealing with the same outcome. Two questions:
Which control should I call Invoke under?
Is there a way to retrieve "the thread that owns the controls underlying windows handle? Can I do this through the Controls.Owner method?
I have tried making this Invoke call using try/catch, but am unable to retrieve an error message in the catch. Any ideas how to resolve the issue this way?
Thanks in advance!
private delegate void stringDelegate(string s);
private void set_textbox_thread(string s)
{
TextBox tbc = get_thread_tb();
if (tbc.InvokeRequired)
{
MessageBox.Show("Invoke Required");
stringDelegate sd = new stringDelegate(set_textbox_thread);
**this.Invoke(sd, new object[] {s });**
MessageBox.Show("Invoke Completed");
}
else
{
1) It doesn't matter what control you invoke under; there is only one UI thread and any control to marshal the call back to that UI thread.
2) what could you possibly do with this thread?
3) not much detail there. How do you know you have error messages? And what do you mean by "error messages"
Call invoke on the text box instead:
tbc.Invoke(sd, new object[] {s });
I have a Winform with 4 PictureBox controls, each control will contain a diferent image. The process is:
An event x is raised, the eventargs from this event, contain the filenames of each image (4), an so on (file exists etc..). Then, I have to update the UI.
Commonly I use Invoke:
Invoke((ThreadStart)delegate()
{
picBig.Image = new Bitmap(strImageBig);
picLittle1.Image = new Bitmap(saLittle[0]);
picLittle2.Image = new Bitmap(saLittle[1]);
picLittle3.Image = new Bitmap(saLittle[2]);
});
// saLittle[] is a string array, contains, filenames: "image1.jpg"
But when this executes, the form freezes for a little time, about 500ms, I know it's a small interval but it's noticeable, then the app continues normally.
I was trying to find out the reason of that 'UI freeze', then, after research, I found BeginInvoke. Now my code looks like this:
BeginInvoke((MethodInvoker)delegate
{
picBig.Image = new Bitmap(strImageBig);
picLittle1.Image = new Bitmap(saLittle[0]);
picLittle2.Image = new Bitmap(saLittle[1]);
picLittle3.Image = new Bitmap(saLittle[2]);
});
This is a little faster. But the UI is still freezing for 200~300ms.
In the articles I've read, they say that BeginInvoke is a better way than Invoke.
The code is working OK, there is no problem with logic or anything else. I just want to know why this happens. I don't want to leave this doubt unclear. The project is already finished. Hope this be useful for someone else.
Maybe this is not the correct approach. I know there are many ways to update the UI from a background thread, but is there another way to make an update faster?
Or, do you think, the image loading is the reason? Is there another way to do a faster loading of images?
Thanks in advance.
This is because you're actually loading your images from disk on the UI thread along with setting the contents of the control. Calling the Bitmap constructor with a file path will go to the hard drive and load the image into memory.
Invoke and BeginInvoke will run the delegate that you provide on the thread that the control was created on, which is most likely going to be the UI thread.
...but is there another way to make an update faster?
Load the images on your background thread and, when they're actually loaded, invoke and set the images into the controls.
var big = new Bitmap(strImageBig);
var little1 = new Bitmap(saLittle[0]);
var little2 = new Bitmap(saLittle[1]);
var little3 = new Bitmap(saLittle[2]);
Invoke((ThreadStart)delegate()
{
picBig.Image = big;
picLittle1.Image = little1;
picLittle2.Image = little2;
picLittle3.Image = little3;
});
But when this executes, the form freezes for a little time, about 500ms, I know it's a small interval but it's noticeable, then the app continues normally.
Eventually, the UI thread needs to actually update the images. When the images are generated and updated on the UI thread, this will cause a (short) delay.
I have a c# form, and the initialization time takes a while (its getting information from a server, and populating a TreeView). Right now, the code looks similar to this:
public class myForm : Form
{
InitializeComponent();
List<Location> locations = getServerLocations(); // Server call
foreach( Location loc in locations )
{
List<POI> POIs = loc.getLocationPOIs(); // Server call
foreach( POI poi in POIs )
{
List<POIDetails> = poi.getPOIDetails(); // Server call
....
}
}
}
you get the point I think ... So there is a large tree, and I know I can not make the calls all the way down until the user expands the tree. But the intent is I just want the Form to display, with a 'loading...' or something on a tool strip while all the processing and server gets are happening.
Right now, it seems as if I haven't loaded the application yet because nothing will show to the user until all the calls are complete.
You shouldn't do any long running processing on the UI thread - instead move this to another thread i.e using a BackgroundWorker. You can initially show the "Loading" screen and, once the background worker completes, update your UI with your tree structure.
You should work with multi threading process, so that you can separate the process that takes time from the rest of the process. Here is a blog that may help you. .NET 4.0 and System.Threading.Tasks
Running your initialization on a separate thread is the preferred way. But if you're constrained to run it on the UI thread then try calling Application.DoEvents() right after your call to .Show() or .ShowDialog() of your form.
If the form shows up, it will still be unresponsive to user actions until the initialization is completed. So running the initialization on a separate thread is the better solution.
Long post.. sorry
I've been reading up on this and tried back and forth with different solutions for a couple of days now but I can't find the most obvious choice for my predicament.
About my situation; I am presenting to the user a page that will contain a couple of different repeaters showing some info based on the result from a couple of webservice calls. I'd like to have the data brought in with an updatepanel (that would be querying the result table once per every two or three seconds until it found results) so I'd actually like to render the page and then when the data is "ready" it gets shown.
The page asks a controller for the info to render and the controller checks in a result table to see if there's anything to be found. If the specific data is not found it calls a method GetData() in WebServiceName.cs. GetData does not return anything but is supposed to start an async operation that gets the data from the webservice. The controller returns null and UpdatePanel waits for the next query.
When that operation is complete it'll store the data in it's relevant place in the db where the controller will find it the next time the page asks for it.
The solution I have in place now is to fire up another thread. I will host the page on a shared webserver and I don't know if this will cause any problems..
So the current code which resides on page.aspx:
Thread t = new Thread(new ThreadStart(CreateService));
t.Start();
}
void CreateService()
{
ServiceName serviceName = new ServiceName(user, "12345", "MOVING", "Apartment", "5100", "0", "72", "Bill", "rate_total", "1", "103", "serviceHost", "password");
}
At first I thought the solution was to use Begin[Method] and End[Method] but these don't seem to have been generated. I thought this seemed like a good solution so I was a little frustrated when they didn't show up.. is there a chance I might have missed a checkbox or something when adding the web references?
I do not want to use the [Method]Async since this stops the page from rendering until [Method]AsyncCompleted gets called from what I've understood.
The call I'm going to do is not CPU-intensive, I'm just waiting on a webService sitting on a slow server, so what I understood from this article: http://msdn.microsoft.com/en-us/magazine/cc164128.aspx making the threadpool bigger is not a choice as this will actually impair the performance instead (since I can't throw in a mountain of hardware).
What do you think is the best solution for my current situation? I don't really like the current one (only by gut feeling but anyway)
Thanks for reading this awfully long post..
Interesting. Until your question, I wasn't aware that VS changed from using Begin/End to Async/Completed when adding web references. I assumed that they would also include Begin/End, but apparently they did not.
You state "GetData does not return anything but is supposed to start an async operation that gets the data from the webservice," so I'm assuming that GetData actually blocks until the "async operation" completes. Otherwise, you could just call it synchronously.
Anyway, there are easy ways to get this working (asynchronous delegates, etc), but they consume a thread for each async operation, which doesn't scale.
You are correct that Async/Completed will block an asynchronous page. (side note: I believe that they will not block a synchronous page - but I've never tried that - so if you're using a non-async page, then you could try that). The method by which they "block" the asynchronous page is wrapped up in SynchronizationContext; in particular, each asynchronous page has a pending operation count which is incremented by Async and decremented after Completed.
You should be able to fake out this count (note: I haven't tried this either ;) ). Just substitute the default SynchronizationContext, which ignores the count:
var oldSyncContext = SynchronizationContext.Current;
try
{
SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
var serviceName = new ServiceName(..);
// Note: MyMethodCompleted will be invoked in a ThreadPool thread
// but WITHOUT an associated ASP.NET page, so some global state
// might be missing. Be careful with what code goes in there...
serviceName.MethodCompleted += MyMethodCompleted;
serviceName.MethodAsync(..);
}
finally
{
SynchronizationContext.SetSynchronizationContext(oldSyncContext);
}
I wrote a class that handles the temporary replacement of SynchronizationContext.Current as part of the Nito.Async library. Using that class simplifies the code to:
using (new ScopedSynchronizationContext(new SynchronizationContext()))
{
var serviceName = new ServiceName(..);
// Note: MyMethodCompleted will be invoked in a ThreadPool thread
// but WITHOUT an associated ASP.NET page, so some global state
// might be missing. Be careful with what code goes in there...
serviceName.MethodCompleted += MyMethodCompleted;
serviceName.MethodAsync(..);
}
This solution does not consume a thread that just waits for the operation to complete. It just registers a callback and keeps the connection open until the response arrives.
You can do this:
var action = new Action(CreateService);
action.BeginInvoke(action.EndInvoke, action);
or use ThreadPool.QueueUserWorkItem.
If using a Thread, make sure to set IsBackground=true.
There's a great post about fire and forget threads at http://consultingblogs.emc.com/jonathangeorge/archive/2009/09/10/make-methods-fire-and-forget-with-postsharp.aspx
try using below settings
[WebMethod]
[SoapDocumentMethod(OneWay = true)]
void MyAsyncMethod(parameters)
{
}
in your web service
but be careful if you use impersonation, we had problems on our side.
I'd encourage a different approach - one that doesn't use update panels. Update panels require an entire page to be loaded, and transferred over the wire - you only want the contents for a single control.
Consider doing a slightly more customized & optimized approach, using the MVC platform. Your data flow could look like:
Have the original request to your web page spawn a thread that goes out and warms your data.
Have a "skeleton" page returned to your client
In said page, have a javascript thread that calls your server asking for the data.
Using MVC, have a controller action that returns a partial view, which is limited to just the control you're interested in.
This will reduce your server load (can have a backoff algorithm), reduce the amount of info sent over the wire, and still give a great experience to the client.