I have a class that has purely static Methods and properties. I am calling an async method on the class "Load" that asks a web service for a chunk of data, which then fires an event that executes the return method, "LoadCompleted". I have no idea how long the call is going to take (the difference between calling the "Load" method, then the "LoadCompleted" getting called).
I would like to block the application from proceeding any further until the callback method has been raised (as the app will try and get stuff from this class, which isn't populated until the "LoadComplete" method sets the data).
How would i go about doing this?
Blocking the main UI thread should be avoided with extreme prejudice.
I would use the BusyIndicator control from the Silverlight Toolkit:-
<UserControl x:Class="StackoverflowSpikes.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit">
<toolkit:BusyIndicator x:Name="busyInd" >
<Grid x:Name="LayoutRoot">
<!-- The rest of your content -->
</Grid>
</toolkit:BusyIndicator>
</UserControl>
Before calling Load use:-
busyInd.IsBusy = true;
then on LoadComplete use:-
busyInd.IsBusy = false;
This will lock out user input on the UI without blocking the main thread and give the use some feedback as to why they can't click anything right now. You can supply your own content for the busy message using the BustContent property. Of course if you don't like the way it looks you can style it to your preference.
If you want to get all MVVM you can bind the IsBusy property to a VM property that indicates that the VM doesn't want anything changing right now.
You can use the ManualResetEvent class to block the main thread if you want. Just call the WaitOne method to block and call the Set method to unblock when the asyc web request completes. Just be aware that if you block your main UI thread, your entire application will become completely unresponsive.
You could consider setting the UI controls to disabled at the start. On load complete you could display your data and then enable the UI controls. No thread blocking is necessary with this approach.
Related
I'm using UWP GridView with DataTemplateSelector to show data for different weeks. When I change the week I want to show loader when data is loading. I'm using MvvmLight for ViewModels binding and when I change the data I'm removing and adding elements to the GridView source. The problem is that when I change IsActive property to true before I run UpdateGrid method, the loader is not active and there is a lag on screen. If data loading (UpdateGrid method) takes more than one sec the loader is visible, so it means for me that the logic there is ok, but the problem can be with generating graphical elements on the screen and performance?
I was trying to make my UpdateGrid method async and sync (there is no api call inside, so can be sync). The method is called in the ViewModel class:
DispatcherHelper.CheckBeginInvokeOnUI(async () =>
{
SyncLoadingImageVisible = true;
await UpdateGrid();
SyncLoadingImageVisible = false;
});
You may be misunderstanding the way async/await works. When you mark a method async ans it contains no real await (meaning no I/O bound operation or operation that actually takes place on another thread), the whole method will essentially run synchronously. This is true in your case as well as you mentioned there is no actual async work inside UpdateGrid so the code will work as if there was really no await.
The UI thread will be busy all the time from the moment you set the SyncLoadingImageVisible to true to the moment you set it back to false - during that time UI thread is 100% dedicated to executing your code so user won't see any UI changes. This causes the behavior you are seeing - that there is a lag as the UI thread does not have a chance to update the UI until the UpdateGrid method finishes executing.
To solve this properly you will have to offload performance intensive, non-UI tasks in UpdateGrid method to another thread using awaited Task.Run and only the code that really does work with the app's UI should then be executed on UI thread. This way you will free the UI thread to be able to display progress to the user while the execution runs in the background.
Here is what my code looks like:
private void exportToExcelButton_Click(object sender, EventArgs e)
{
txtBox.Clear();
txtBox.AppendText("Beginning Export...");
ExportExcel(txtBox);
txtBox.AppendText("Export complete...");
}
The problem I am having is that whenever the button is clicked (to execute the function above), only part of the current text in the TextBox (System.Windows.Forms.TextBox) is cleared, and replaced with the first line: "Beginning Export ...".
However once the function ExportExcel(txtBox) is done executing, then the entire text is replaced by the new one generated in ExportExcel(txtBox).
Inside ExportExcel(txtBox); I have several txtBox.AppendText() statements explaining to the user the actions being made.
I have tried clearing the text with txtBox.Text = String.Empty; and txtBox.Text = "";and neither have worked.
Let me know if anything needs to be clarified, thanks.
Looks like you're blocking the GUI thread, preventing the text box from redrawing itself. This is one reason why you shouldn't perform long-running tasks on the GUI thread. Use a background thread instead. That way you leave the GUI thread free to perform important operations like drawing, responding to mouse clicks, etc.
Have you tried the textBox.Refresh , before calling txtBox.AppendText("Beginning Export...").
The method invalidates the control.
On the other hand, if you use a background thread, then you should update the UI only by overriding the Progress Changed event. Background threads are not meant for updating user interfaces. Try searching for Worker threads and UI threads. They correlate to MFC, but the concept is the same.
Also keep in mind the cross thread calls.
I agree with dvnrrs. However if you are unable to do this, try calling txtBox.Refresh();after adding each line of text.
There is another method called Application.DoEvents(); that has a similar behavior, but its use is not recommended since it sort of short-circuits the normal application flow and can cause your application to fail unexpectedly or do strange things.
I have a program that runs a series of methods in other threads within one window and let's the user know what's going on using a status bar. The status bar updates are in the main thread which set's the status bar and then refreshes the GUI. There are several blocks of code in series each looking something like this:
Thread do1Thread = new Thread(Class.Method);
do1Thread.Start();
// inform user
this.status.Text = "Doing stuff 1...";
// update GUI
Utility.RefreshGUI();
// join thread
do1Thread.Join();
Sometimes the status bar does indeed update but often is stays on the first status until the end when it displays the last status. Occasionally is sticks on "Ready." which is the default.
Note that two of the blocks take a few seconds so there should be time for it to update. Also, the program is written in C# (Mono) using GTK# for the GUI.
How can I ensure that that the GUI updates to reflect the change?
The problem is that the Join() call blocks the UI thread which blocks all window messages.
Can you use a BackgroundWorker and execute whatever code you have after the Join in the RunWorkerCompleted call?
You need to dispatch Update message to UI thread, call invoke instead of direct property
this.status.BeginInvoke((Action)(() => this.status.Text = "Something happen"));
The best way I have found to update a control in a primary thread is to set a delegate for updating and invoke that from other threads.
You have to use observe and observable pattern.
EDITED:
It is really better divide logic and view parts of code.
Here is an example in real world how to use. Pattern
Could you check whether you are using a StatusStrip control?
If so, your code looks like setting directly the Text of Stautus Strip Control
this.status.Text = "Doing stuff 1...";
So it wont reflect in the Status Strip as Text. You have to place a toolstriplabel and need to set its text in this case.
Please check the post here
I'm modifying existing WinForms project. The project has UserControl.
This UserControl has DataSet variable which is set from another part of the program in different thread.
What I want to do is to dynamically add another controls to this control depending on the DataSet.
So, after DataSet is loaded, I'm calling RefreshChildControl function and trying to add my new ChildUserControls to flowLayoutPanel. And that's where the problems begin:). I get the "Cross-thread operation not valid: Control 'ChildUserControl' accessed from a thread other than the thread it was created on" exception. I tried to use if(this.InvokeRequired) and Invoke this method, but it does not help. InvokeRequired on MyUserControl is false.
So, is there any good way of performing such task? Or am I missing something important?
EDIT:
I tried to skip InvokeRequired test and just call this.FindForm().Invoke on this method. I've got "Invoke or BeginInvoke cannot be called on a control until the window handle has been created." exception. And, by the way, when I open another form with this control everything worked fine.
First. The simplest solution is to perform Invoke everytime. Nothing bad will happen.
Second, use SynchronizationContext.
using System.Threading;
public class YourForm
{
SynchronizationContext sync;
public YourForm()
{
sync = SynchronizationContext.Current;
// Any time you need to update controls, call it like this:
sync.Send(UpdateControls);
}
public void UpdateControls()
{
// Access your controls.
}
}
SynchronizationContext will manage all threading issues for you. It checks, whether you call from the same or from the other thread. If from same it will just immediately execute your code. Otherwise it will do Invoke through form's message loop.
If your user control is not immediately visible after you construct it, the handle will not be created on the thread that you think it is created on. It's not the C# object whose thread parent is important, it is the Windows Handle object whose parent is important.
To force a control to be immediately created on the thread that you thought you created it on, then
read out the control.Handle which will force the control to actually be made and assigned a handle.
MyUserControl uc = new MyUserControl(); // the handle is not created here
uc.Visible = false;
IntPtr dummy = uc.Handle; // The control is immediately given a real handle
You can also try to fiddle around with uc.CreateControl, but this won't create the handle if the control is not visible.
Now you can have another thread update your user control even if the user control is not visible.
uc.BeginInvoke((Action)(() => uc.Text = "ha ha"));
If you leave out the dummy = uc.Handle line, you will get an exception that you can't call BeginInvoke on a control that does not have a handle.
http://msdn.microsoft.com/en-us/library/system.windows.forms.control.createcontrol(v=vs.90).aspx
Now unfortunately due to the fact that WinCE Usb Device Arrival / Removal exposes itself via WindowsMessages I have to ensure that a certain (non-UI) component is not created on a background thread. I would like to assert this via an exception but am lacking the code for the assertion.
This component creates a MessageWindow* and uses it to receive usb arrived/removed messages. The issue is if someone creates this component on a background thread (not necessarily; IsBackground = true) when the thread exits the window will be destroyed.
Any ideas?
*as an aside I still don't know why Form doesn't inherit from this class
Update
I think my version 1 wasn't very clear. So this is v2.
When you create a MessageWindow or a Form for that matter on a thread, when that thread exits the Window/Form is destroyed.
My component is creating a "hidden" message window to intercept some important events, ergo I do not wish for it to be destroyed. Therefore I must somehow ensure that the code that creates the form is running on the "Main UI" thread.
If possible i'd like to avoid passing down a reference to the "main" form to this component as it is (architecturally speaking) supposed to be miles away from the UI.
Update
Moving logging question to a separate Q.
Ok, I understand that you don't want for your component to "know" about the main window -- makes sense.
How about this: How about if you make sure that you always instance your component on the main thread? You component will create it's listener window on the constructor's thread.
If you do that, then you just need to make sure that you call the constructor from the main thread. I'm making some assumptions about your code, but I'm guessing that you must have some class in your architecture that knows about both the UI and your component. Create your component there, using a callback, and the main form's InvokeRequired/Invoke methods.
In forms, you use the InvokeRequired property.
Why not create the non-UI component on a background thread and when you go to update any UI component just look to see if invokeRequired then get back on the main thread to actually do the update.
You should have nothing really tying up the main event thread, IMO.
You can use it in this way:
void MyCallback()
{
if (form1.InvokeRequired) { // form1 is any existing gui control
form1.Invoke(new Action<>(MyCallBack));
return;
}
// your logic here
}
Hey there: I had an idea about your problem. This is just a random thought, and I don't know for sure whether it will work (I have not tested, nor even compiled this -- it just hit me):
What if you get the window handle of the main window of your app, then build a Control around it (I'm assuming that you have a gdi-based app, like Winforms)?
this code might not compile, but it's close (it would go into your component -- note that it would make your component require a gdi windows/winform app, as opposed to a console or WPF app).
If you do try it, I'd love to hear whether it worked for you.
using System.Diagnostics;
using System.Windows.Forms;
void Init()
{
// get handle to the main window
intPtr mainWindowHandle = Process.GetCurrentProcess().MainWindowHandle;
Control mainWindow = Control.FromHandle(mainWindowHandle);
if(mainWindow.InvokeRequired)
mainWindow.Invoke(SetupMessageWindow);
else
SetupMessageWindow();
}
void SetupMessageWindow()
{
// do your thing...
}