I have seen several similar questions on SO and elsewhere, but none seems to work for me.
I have a small Window in my project containing a LoadingAnimation that I show up at application startup and want to keep actual processing running. Here's my startup code:
Dim WaitWindow As New WaitWindow("Loading application...")
WaitWindow.Show()
LongRunningLoading()
WaitWindow.Close()
Here's LongRunningLoading() function that I try to run on a separate thread to avoid blocking my animation:
Private Function LongRunningLoading() As Boolean
Dim resetEvent As New System.Threading.ManualResetEvent(False)
Dim RetVal As Boolean = False
ThreadPool.QueueUserWorkItem(Sub(state)
'DO SOMETHING AND RETURN RESULTS
resetEvent.Set()
End Sub,
RetVal)
resetEvent.WaitOne()
Return RetVal
End Function
Everything works as expected except that the loading animation doesn't play. What am I doing wrong?
What am I doing wrong?
You're doing this:
resetEvent.WaitOne()
That blocks the UI thread. Don't do that. Instead, remember that the UI is basically event based - unless you're using the async features in VB 11, you'll have to write your code in an event-based way. So basically when your long-running task completes, you need to post back to the UI thread to execute the WaitWindow.Close() part.
If you can use .NET 4.5 and VB 11, you can use Task.Run to start a new task for your long-running work, and then Await that task from an asynchronous method.
They are both running on UI Thread, this is why loading animation is waiting. Try to use BackgroundWorker for your LongRunningLoading process and then return to UI thread if needed for your results.
This approach worked for me:
Dim waitwindow As New WaitWindow("Loading application...")
ThreadPool.QueueUserWorkItem( _
Sub()
LongRunningLoading()
Dispatcher.Invoke(New Action(AddressOf waitwindow.Close))
End Sub)
waitwindow.ShowDialog()
May help someone else.
Related
I have a Winforms application with a primary form that contains (among other things) a Telerik DocumentTabStrip. These tabs are used to hold user controls or web pages (via a web browser control). It has worked fine for quite a while, but I'm running into an issue now.
I recently switched the web browser control from the built-in .NET web browser based on IE to CefSharp. Since doing so, I've noticed that occasionally when trying to add the DocumentWindow to the DocumentTabStrip, the call will hang indefinitely (in debug) or crash outright (running the app normally). This only appears to happen when opening a DocumentWindow that contains the browser control, not any other user controls. The actual call itself is below.
I'm at a bit of a loss as to how to even begin to debug this, since there's no error that gets received - it just hangs inside the Controls.Add() method indefinitely. Any advice would be appreciated.
Private dts As New Telerik.WinControls.UI.Docking.DocumentTabStrip
Try
dts.InvokeIfRequired(Sub()
Dim docWindow As Telerik.WinControls.UI.Docking.DocumentWindow = Nothing
Dim ctrl As ucBaseControl = Nothing
Dim browser As ucBrowser = Nothing
Dim isBrowser As Boolean = False
docWindow = New Telerik.WinControls.UI.Docking.DocumentWindow
docWindow.BackColor = Color.FromArgb(89, 89, 89)
'Do various stuff to determine the type of control to load (ctrl or browser), then setup the applicable control
If isBrowser Then
'Place the browser into the Document Window.
If Not IsNothing(browser) Then
browser.Dock = DockStyle.Fill
docWindow.Controls.Add(browser)
End If
Else
'Place the ctrl into the Document Window.
ctrl.Dock = DockStyle.Fill
docWindow.Controls.Add(ctrl)
End If
'Add the DocumentWindow to the DocumentTabStrip
' Ensure DockWindow not disposed due to lag in bringing up
If IsNothing(docWindow) OrElse docWindow.IsDisposed Then
Exit Sub
End If
Try
docWindow.Padding = New Padding(0)
dts.TabStripElement.Children(0).Children(1).Padding = New Padding(0)
dts.Controls.Add(docWindow) 'This is where the issue is. It only happens sporadically here.
Catch ex As Exception
'Code to log any exceptions here. In the problem described here, no exception is ever generated, though.
End Try
'Bring the control to the front and focus it, here...
End Sub)
Catch ex As Exception
'Error handling code here'
End Try
I'm assuming InvokeIfRequired is an extension method you've created for Controls. Note that if it relies on Invoke, that is a synchronous call, instead use BeginInvoke (see: What's the difference between Invoke() and BeginInvoke())
No exception was ever thrown because you were suffering from deadlock
I didn't think this actually happened, but it appears that my WPF app (window) is not calling a Sub asynchronously, or something that looks like this... The use case is deceivingly simple:
wpfw = WPF Window
StatusLeft = textbox on wpfw
cmds = Command class instantiated in wpfw class to store all window commands (subs). the
wpfw instance is passed to it in the constructor.
UpdateStatusLeft = a Sub in cmds that looks like this:
Private Sub UpdateStatusLeft(UpdateText As String)
Try
Me.wpfw.Dispatcher.Invoke(CType(Sub() Me.wpfw.StatusLeft.Text = UpdateText, Action))
Catch ex As Exception
[...]
End Try
End Sub
The above works, but only updates the main window after the long running sub in cmds is finished. Calling that long running command is nothing special:
(XAML)
<MenuItem Header="Process Files" Click="mnuReference_ProcessFiles"/>
in wpfw, the handler for the click (mnuReference_ProcessFiles) is this:
Private Sub mnuReference_ProcessFiles(sender As Object, e As RoutedEventArgs)
Me.cmds.ParseFiles()
End Sub
Now cmds is instantiated as soon as the wpfw is, and the sub it is pointing to (mnuReference_ProcessFiles) looks like this:
Public Sub ParseFiles() Implements Icmds.ParseFiles
Try
Dim fu As FileUtils = New FileUtils()
Me.UpdateStatusLeft("Starting Batch 1...")
ipu.ParseFile(".\batch1")
Me.UpdateStatusLeft("Starting Batch 2...")
ipu.ParseFile(".\batch2")
[...]
Above, "Me.UpdateStatusLeft" is in the cmds class. In fact, I put a Sub UpdateStatusLeft in every class the mainwindow calls (including it's own class!), and I pass down the wpfc instance to each command/processing class.
If you try to update the textarea directly frm another thread/class other than the wpfw one, you get a thread error-which is why I use Dispatcher.Invoke(...
Clarity:
The commands are all firing off as expected, and do their job well. That never was an issue.
The issue is trying to find a way to update the top/originating UI thread's textarea as those tasks progress so the user doesn't fall asleep or think the program has crashed, etc.
The main application dispatcher uses the UI thread, so long-running processes will still lock up the UI thread
The typical solution is to run long-running processes in a background thread, then use the dispatcher to update your UI objects on the main thread once it's finished.
Or if you use something like the Task Parallel Library, it allows you to schedule your task for UI thread, and bypass the dispatcher completely. Here's an example:
Task.Factory
.StartNew(() =>
{
// This runs in a background thread
return GetLongRunningProcessResults();
})
.ContinueWith.StartNew((t) =>
{
// This runs in the UI thread because of the
// TaskScheduler.FromCurrentSynchronizationContext parameter
UpdateUI(t.Result);
}, TaskScheduler.FromCurrentSynchronizationContext());
Wrap the code in ParseFiles in a call to Tasks.Run.
If you want your application to stay responsive, you must execute intensive tasks in another thread than the main thread.
See bottom of post for my pseudo solution.
Once again I'm completely and utterly stuck on this. I've burned hours trying to understand - and yes I can get a single collectionviewsource to work beautifully with nothing about threading on the code behind.
Imagine my shock when I found merely adding two collectionviewsources on the page causes threading issues. I've spent a few hours last night reading Async in C#5 and the MSDN stuff however I get into work today and I can't decipher how to make this happen.
The code below is the last attempt I've made before whining for help as I've burnt, possibly, a bit too much work time on attempting to understand how to do this. I understand that I need one collectionviewsource to complete before starting the other, so I tried Await Task.ContinueWith etc to try and chain one after the other.
Lining up both sets of tasks in the threads correctly seems to be quite tricky, or I'm still misunderstanding something fundemental.
If anyone can advise how they would asynchronously populate a few controls on a WPF UI I would be very grateful.
The application itself is a throwaway application, linked to an Access database that I'm using to try and become fluent enough in threading to implement it in our proper code base. I'm long way off that!
Updated with more complete code samples and the adjustments made according to answers:
Private Async Sub MainWindowLoaded(sender As Object, e As RoutedEventArgs) Handles MyBase.Loaded
InitializeComponent()
Dim personSetViewSource As System.Windows.Data.CollectionViewSource = CType(Me.FindResource("personSetViewSource"), System.Windows.Data.CollectionViewSource)
Dim contactSetViewSource As System.Windows.Data.CollectionViewSource = CType(Me.FindResource("contactSetViewSource"), System.Windows.Data.CollectionViewSource)
Dim personList = Await Task.Run(Function() personSet.personList)
personSetViewSource.Source = personList
Dim contactList = Await Task.Run(Function() contactSet.contactList)
contactSetViewSource.Source = contactList
End Sub`
The ObservableCollectionEx class:
public class ObservableCollectionEx<T> : ObservableCollection<T>
{
public override event NotifyCollectionChangedEventHandler CollectionChanged;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
using (BlockReentrancy())
{
NotifyCollectionChangedEventHandler collectionChanged = this.CollectionChanged;
if (collectionChanged != null)
foreach (NotifyCollectionChangedEventHandler nh in collectionChanged.GetInvocationList())
{
DispatcherObject dispObj = nh.Target as DispatcherObject;
if (dispObj != null)
{
Dispatcher dispatcher = dispObj.Dispatcher;
if (dispatcher != null && !dispatcher.CheckAccess())
{
NotifyCollectionChangedEventHandler nh1 = nh;
dispatcher.BeginInvoke(
(Action) (() => nh1.Invoke(this,
new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Reset))),
DispatcherPriority.DataBind);
continue;
}
}
nh.Invoke(this, e);
}
}
}
}
Please note, I can't translate this class to VB due to requiring an Event Override.
Another variation I've tried, but falls foul of thread ownership again. The two collectionviews thing isn't yielding to a solution: I don't know if it's because the underlying collection isn't good for it or whether in reality it wasn't meant to work that way. I get close but no cigar.
Dim CarePlanList = Task.Run(Function() CarePlanSet.CarePlanList)
Dim rcpdList = Task.Run(Function() rcpdSet.rcpdList)
Dim tasks() As Task = {CarePlanList, rcpdList}
Dim t = New TaskFactory
Await t.ContinueWhenAll(tasks, Sub()
carePlanSetViewSource.Source = CarePlanList
rcpdSetViewSource.Source = rcpdList
End Sub)
I found a way to do it, based on a combination of feedback and research this morning. Building the two collectionviews asynchronously itself is somewhat impractical given the STAThread model of WPF. However, merely ensuring one HAS completed and shifting some of the async out of one entity class has made this plausible.
Instead I fire off the first task, who's underlying class does build it's data with its own Async method. Then test to see if it has completed before allowing the second collectionview to be fired off. This way I don't need to worry about context or dispatcherobjects. The second collection does not use any async.
Dim personList = Task(Of List(Of person)).Run(Function() personSet.personList)
Dim contactList = Task(Of ObservableCollectionEx(Of contact)).Run(Function() contactSet.contactList)
contactSetViewSource.Source = contactList.Result
If contactList.IsCompleted Then personSetViewSource.Source = personList.Result
This is an experimental project for concept research really. As it happens, the idea I'd want two such lists built this way isn't as useful as all that but I do see where being able to compose a data heavy interface asynchronously could be handy.
Your two code samples each have issues that jump out right away.
In the first you are awaiting task1, I assume with more code following, but all task1 is doing is starting what is basically a fire and forget operation back to the UI thread (Dispatcher.BeginInvoke), therefore not really producing anything asynchronous to await.
In the second, the primary issue seems to be that you are doing a lot of setup of Task instances and chaining them with continuations but never starting the action2 Task, which appears to be the root of the whole chain, hence getting no activity at all. This is similar to what you get with a BackgroundWorker that never has RunWorkerAsync called.
To get this working properly and avoid making your head spin any more I would suggest starting by writing this whole block without any async and verifying that everything loads as expected, but with the UI lockup you want to avoid. Async/Await is designed to be added into code like that with minimal structural changes. Using Task.Run along with async and await you can then make the code asynchronous.
Here's some pseudocode for the basic pattern, without async to start:
PersonSetList = LoadData1()
CVS1.Source = PersonSetList
ContactList = LoadData2()
CVS2.Source = ContactList
and now adding async:
PersonSetList = await Task.Run(LoadData1())
CVS1.Source = PersonSetList
ContactList = await Task.Run(LoadData2())
CVS2.Source = ContactList
What this will now do is start a task to load the person data and immediately return from your WindowLoaded method, allow the UI to continue rendering. When that data is loaded it will continue to the next line on the original thread and push the data into the UI (which may itself slow down the UI while rendering). After that it will do the same for the Contact data. Notice that Dispatcher isn't needed explicitly because await is returning back to the UI thread for you to complete its continuation.
When you have a button, and do something like:
Private Function Button_OnClick
Button.Enabled = False
[LONG OPERATION]
End Function
Then the button will not be grayed, because the long operation prevents the UI thread from repainting the control. I know the right design is to start a background thread / dispatcher, but sometimes that's too much hassle for a simple operation.
So how do I force the button to redraw in disabled state? I tried .UpdateLayout() on the Button, but it didn't have any effects. I also tried System.Windows.Forms.DoEvents() which normally works when using WinForms, but it also had no effect.
The following code will do what you're looking for. However I would not use it. Use the BackgroundWorker class for long time operations. It's easy to use and very stable.
Here the code:
public static void ProcessUITasks() {
DispatcherFrame frame = new DispatcherFrame();
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(delegate(object parameter) {
frame.Continue = false;
return null;
}), null);
Dispatcher.PushFrame(frame);
}
Here you will find a sample on how to use the BackgroundWorker.
InvalidateVisual(); #HCL is right... don't do this
Like you say, it is better to start use a background thread / dispatcher and keep the UI thread unblocked. Consider looking at the Reactive Extensions library from Microsoft for high level asynchronous ui programming
In Windows.Forms, you can Button.Refresh().
In Windows.Forms or WPF, you can yield to the message pump to let it redraw. Async/Await were designed to allow you to do this without the nastiness of HCL's answer.
Private Async Function Button_OnClick
Button.Enabled = False
Await Task.Yield
[LONG OPERATION]
End Function
I have some code that iterates a few 100 urls and requests the data from the web.
It looks something like this
for each url in urls
Dim hwr = CType(WebRequest.Create(url), HttpWebRequest)
Dim rq = New ReqArgs
rq.Url= url
rq.Request = hwr
Dim res =
hwr.BeginGetResponse(New AsyncCallback(AddressOf FinishWebRequest), rq)
Dim a = 1
next
Does this look ok?
How come the BeginGetresponse line takes about 2-3 seconds to complete before going to dim a=1?.
Actually I debugged and I see that the FinishWebRequest procedure runs completely before the Dim a=1 is reached.
So is this async?
I'm not earning any time by using the async. am I? Or is there a different way to do this?
The point is that the main sub should fire off 300 requests and return control to the UI, then the FinishWebRequest should process them slowly on its own thread and own time , as the requests come in.
How do I do that?
Btw, the main sub is running in a BackgroundWorker, but I checked with out the BackgroundWorker and the problem is the same
It seems that the answer should be here but its just not working for me
I'm WPF 4.0
Appreciate your help and advice. Thanks
yup
the problem was with the POST
i now start the post writing like this
Dim ReqStream = hwr.BeginGetRequestStream(New AsyncCallback(AddressOf FinishRequestStream), rq)
and then my callback is like this
Sub FinishRequestStream(ByVal result As IAsyncResult)
Dim ag = CType(result.AsyncState, ReqArgs)
Dim postStream = ag.Request.EndGetRequestStream(result)
Dim PostBytes = Encoding.UTF8.GetBytes(ag.PostText)
postStream.Write(PostBytes, 0, PostBytes.Length)
postStream.Close()
Dim res = ag.Request.BeginGetResponse(New AsyncCallback(AddressOf FinishResponse), ag)
End Sub
hope this helps someone in the future
Reposting this from another question.
From the documentation on HttpWebRequest.BeginGetResponse Method:
The BeginGetResponse method requires some synchronous setup tasks to complete (DNS resolution, proxy detection, and TCP socket connection, for example) before this method becomes asynchronous. [...]
it might take considerable time (up to several minutes depending on network settings) to complete the initial synchronous setup tasks before an exception for an error is thrown or the method succeeds.
To avoid waiting for the setup, you can use
HttpWebRequest.BeginGetRequestStream Method
but be aware that:
Your application cannot mix synchronous and asynchronous methods for a particular request. If you call the BeginGetRequestStream method, you must use the BeginGetResponse method to retrieve the response.