.NET Framework Developer Center >
.NET Development Forums
>
.NET Base Class Library
>
Aynchronous Applications - How do you write yours? More from the custom control side of things
Aynchronous Applications - How do you write yours? More from the custom control side of things
- Hi all,
I was wondering what would be the best practice for writing asynchrounous applications. In todays day and age it is important to have a fully responsive UI from a user perspective. With the .NET framework, we have a pretty good framework for developing asynchronous applications using threadding, async patterns, background workers.
I've wrote a number applications that use bacground processes now and I'm embarking on my next venture which got me thinking.
For my async applications, I have used Threads to run various processing tasks in the background which have ran fine. There was a small amount of work that would inform the UI of the progress of various tasks. For asynchronous applications, you cannot update the main UI from a seperate thread. So for this you would determine if you could use update the control directly using control.InvokeRequired. If you can't then you need to create your own delegate and create a method handler to do this.
So putting this simply using the BackgroundWorker component (in VB.NET):
Public Class Form1 ' delegate to manage the callback across to UI thread Private Delegate Sub SetTextDelegate(ByRef textBox As TextBox, ByVal message As String) Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork For i As Integer = 0 To 20000 ' this is running on a seperate thread so we cannot update the textbox directly UpdateTextbox(Me.TextBox1, Date.Now.ToString() & "Worker 1 Running " & System.Environment.NewLine) ' delay this thread for a second System.Threading.Thread.Sleep(1000) Next End Sub Private Sub UpdateTextbox(ByRef textBox As TextBox, ByVal message As String) ' determine if we can update the control directly If textBox.InvokeRequired Then TextBox1.Invoke(New SetTextDelegate(AddressOf UpdateTextbox), New Object() {textBox, message}) Else TextBox1.Text &= message End If End Sub Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click Me.BackgroundWorker1.RunWorkerAsync() End Sub End Class
All is simple enough.
But as we add complexity to the system, we may want to create numerous controls, classes that each have a part in updating something in the UI. Consider the following scenario (hoping this builds a clear picture):
Suppose I have a class, Class1, that maintains a list (List<>, List(Of...)) of strings. Class1 has a single event that is raised whenever an item is added to this array. Let's say this event is called ItemAdded with the signature:
Sub ItemAdded(ByVal sender As Object, ByVal e As ItemAddedEventArgs)
void ItemAdded(object sender, ItemAddedEventArgs e);
Class1 has a single public method that allows items to be added to the array. Let's say this method is called AddItem. Class1 is just a class - it does not inherit anything - well, except System.Object!
Form1 is a form in the application that runs a BackgroundWorker. The BackgroundWorker is reponsible for adding items to this array. Form1 has a single instance of Class1 as a private field member.
Form1 has a UserControl1 - a custom user control - that sits that inside of it. UserControl1 has an reference to the instance of Class1 in Form1.
When the process runs, the BackgroundWorker will add items that are then in-turn raised to the UserControl1 control event handler for AddItem. The event is not raised from the UI thread, but on the thread that the background worker runs on.
So in the event handler code for ItemAdded of Control1, if you do not determine if the event was raised on the same thread of the control (using InvokeRequired) you will be faced with the exception:
"Cross-thread operation not valid: Control 'SomeControl' accessed from a thread other than the thread it was created on" exception.
I'm guessing that when most of us write user controls, we quite frequently never code the InvokeRequired in to situations like this. Would this be the best practice for writing any control - not just in multi-threadded/async applications?
I just want to know what peoples take is on this?
Cheers
Andez (Please mark as answer if it helps)
All Replies
- Why don't you just simply call the method, backgroundworker.ReportProgress , to update the UI from a BGW?
It was seemingly designed expressly for updating winforms controls.
The event handler, worker_ProgressChanged, runs on the UI thread. No more invokes required.
Any events that get fired get handled on the main thread.
Meanwhile the BGW is till going about its' business, periodically reporting progress as it does its thing.
Rudy =8^D
Mark the best replies as answers. "Fooling computers since 1971." - Another piece of advice when it comes to threads.
Pay attention to variable scope.
You can stay out of 'cross=thread' trouble by only referencing variables declared within the thread's scope.
In other words, try not to reference variables declared at the class level inside of worker_DoWork event handler.
Either declare all variables locally, or pass the object references into the worker thread by using the overload of RunWorkerAsynch(objectInstance).
Only reference variables declared within worker_DoWork itself. Ditto for any methods you call from DoWork.
Mark the best replies as answers. "Fooling computers since 1971." - Rudedog2 has some very pertinent advice, however to add my own two cents, I tend to go about things differently. I'm more of an automation king, so I like things to be designed to work once, and then I don't have to redesign them.
That being said, I often, when dealing with Threads have to ask myself two simple questions:
1) is this a FNF (Fire'n'Forget)?
2) Will this be marshalling back and forth across thread line?
now, for FNF threads, those can even have the progress reporting as Rudedog2 suggested, and for those, well the BackgroundWorker is just find. Handle the DoWork() event, and then through the FNF process occasionally worker.ReportProgress() and all is hunky dory.
For co-existent threads that may or may not need to communicate back and forth with each other or the main UI thread, that's where I tend to suggest Refractor. It is so wonderful, because yest the BackgroundWorker is all well and good, but it is limited. Enter refractor and we see that all backgroundworker is is a wrapper for the ThreadPool and the AsyncOperation class.
AsyncOperation has 2 wonderful methods: Post() and PostOperationCompleted()
Now, you may or may not need the OpCompleted method, but the Post() method is wonderful. the usage is as follows:
asyncOp = AsyncOperationManager.CreateOperation(userstate)
_thread = New Thread(addressof ThreadCallBack)
_thread.Start()
sub ThreadCallBack()
AsyncOp.Post(AddressOf CallBack, Arg)
end sub
What this does, is at the time of creation, the AsyncOp object retains which thread it is created on. This is helpful if you are spawning threads from the UI thread, or from a nother worker thread. so UI spawns worker1, worker1 spawns worker2. in this situation AsyncOp1 posts back on the UI thread, where AsyncOp2 posts back from worker2 on Worker1's thread. with this in mind, i've designed an entire model based upon this, so that say i've got that "ItemAdded" event you were discussing. Well, in this situation, you need to A) determine if what is occurring on the ItemAdded() event is interacting with controls on the UI thread or if it doesn't need to. If it only needs to maintain thread scope, go ahead and keep using the RaiseEvent. However, if the ItemAdded needs to be executed on the Calling Thread (UI thread in this case) then you can use this as a workaround:
AsyncOp.Post(addressof Callback, ItemAddedEventARgs)
sub Callback(args as Object)
RaiseEvent ItemAdded(me, directcast(Args, ItemAddedEventArgs))
end sub
Now, this at first can be very annoying, and daunting in comparison the simplicity of the BackgroundWorker component. However, once you investigate this, I have an entire Family of objects that are based upon a base level event communication, thus I can Simply Execute a Command (like the BackgroundWorker.RunWorkerAsync()) and within the command object:
OnProgressEvent(current, max)
PostEvent(SomeNotificationEvent, Args)
OnCOmpleteEvent()
And the system knows if i'm running asynchronously, and post's the events back to the parent thread without a hitch. This is more advanced methodology than the background worker, but i find it is far more robust.
If you'd like i can provide some more direct code samples. (once i finish documenting them *snicker*)
Jaeden "Sifo Dyas" al'Raec Ruiner
"Never Trust a computer. Your brain is smarter than any micro-chip." - Hi guys,
Thanks for the input. Some good points there.
I've used the ASync operation classes in a client/server scenario - running inside a server project (Windows Service) that sent messages over TCP/IP or remoting at various intervals but not so much in the same application as the UI (need to check as it was a while ago). I still needed to do some wizardry to handle the callback on the same thread as the UI to update using the InvokeRequired/Invoke.
I thought I'd try the BackgroundWorker this time. As for reporting progress back on the UI thread using ProgressChanged - ProcessChangedEventArgs.UserState property. Not the best documentation I've come across mind.
Depends what I'm processing in my worker process, I may want to update 'progress'/give feedback in a progressbar, list view, treeview, textbox, icon status in a picture control. Each will take different information. So in this case I feel the ASync operation classes would be better - maybe wrapped up in a seperate class that raised events based on the action that it was performing eg. FileProcessed, FileMoved, CreatedReceiptFile, InspectingData, DataInspected, etc.
As for refractor or is it refactor? Refractor - .Net tool on Sourceforge?
Thanks
Andez (Please mark as answer if it helps)
The APM appears throughout the framework.
The BackgroundWorker class just seems to me as if it designed specifically for updating the UI for Winforms apps.
Sifo Dyas, is correct when he described the BGW as a wrapper class.
As for the need to update different controls based upon what task is performed, simply call different event handlers.
I posted a commented sample in the MSDN library that demonstrates how ReportsProgress(int, object) could be used.
You have the ability to 'marshall' an object back to the UI thread.
I once experimented with sending delegates wrapped up inside of a class with object parameters, and not unexpectedly it worked.
Good luck and Happy Coding.
Rudy =8^D
Mark the best replies as answers. "Fooling computers since 1971."- RedGate's .Net Refractor - One of the most useful tools for any .net Developer.
As for Event posting on the UI...Well this was my basic solution:
Given This Class:
These actually Came from my SQL Execution class, where I parse multiple SQL Commands (separated by ;) from a RichText and convert them into SqlCommand objects to be executed against the DB. However, if there is more than one command well, I have to work some nifty magic, because some are select statements, others could be insert statements.Public Class MyClass Inherits MyAsyncCommand #Region "-------------::< Events & Delegates >::-------------" Public Delegate Sub CommandExecuteEventHandler(ByVal sender As Object, ByVal e As CommandExecuteArgs) Public Event CommandExecute As CommandExecuteEventHandler Public Event CommandError As CommandExecuteEventHandler Public Event CommandSuccess As CommandExecuteEventHandler Public Event ParseError As CommandExecuteEventHandler Public Event BeginExecute As CommandExecuteEventHandler #End Region End Class
So the BeginExecute Event tells my UI that Sql COmmands Are being Executed, so I have to set up my interface. My SplitContainer that was hiding Panel2 containing a TabPage control, has to be made visible, as well, the number of commands needs to be known, so I know how many tabs to add to the TabControl.
ParseError/CommandError well those, do update the UI with a New Icon per Tab Page indicating a failure of some Kind, as well, as there is the opportunity to Raise a msg to the user and allow them to cancel the operation. CommandExecute means I'm about to actually Execute a single SqlCommand against the DB so i need to update the icon and tab name since in know what the command is being executed. (i also reformat the text of the command so it looks neater in the RichTextBox). and finally CommandSuccess tells me that the command has completed with no errors, and if it was a select statement, the event passes the filled DataTable object as well, which is attached to the DGV created on the tab. (Created during the COmmandExecute event, if the Command was a SELECT statement).
NOw all of these happen on the UI thread, where the parsing and execution of the mutliple sql commands takes place on a worker thread. how I resolved this:
First: Well Yes I have the MyAsyncCommand object which not only has the built in support for asynchronous operations. But I'll go over the basics of how mine is designed.
In the above class I basically made a form, a mold - template if you wish for how i go about things in the future. now, If i need a asynchronous operation to communicate back with parent thread, I start by inheriting this class. How to use it, is just as straightforward:Public Class AsyncCommand() 'Let us assume we have a Progress Event, and a Complete Event 'And the PerformOp event with appropriate Delegates. 'Progress takes a ProgressEventArgs, with a Current, Max, UserState 'Complete just takes a UserState private _async as AsyncOperation private _thread as Thread public property IsAsync() as boolean //assume this does what it says, tests for if we are running in Asynchronous mode Public Function Execute(byval Arg as Object) as Boolean try PerformOp.Invoke(Arg) Catch Finally OnCompleteEvent() End Try Return Result End Function Public Sub ExecuteAsync(Arg as Object) if Not IsAsync then _async = AsyncOperationManager.CreateOperation() _thread = new Thread(addressof ThreadProc, Arg) _thread.Start() End If End SUb Public Sub ThreadProc(Arg as Object) Execute(Arg) End Sub Protected Overridable Sub PostEvent(ByVal SynchronizedCallback As Threading.SendOrPostCallback, ByVal e As EventArgs) If IsAsync AndAlso _Async.isValid() Then _Async.Post(SynchronizedCallback, e) Else : SynchronizedCallback.Invoke(e) End If End Sub Protected Overridable Sub PostWaitEvent(ByVal SynchronizedCallback As SendOrPostCallback, ByVal e As EventArgs) If IsAsync AndAlso _async.isValid() Then Dim postWait as New ManualResetEvent(False) _Async.Post(AddressOf AsyncPostWait, New PostWaitStructure(SynchronizedCallback, e, postWait)) postWait.WaitOne() Else : SynchronizedCallback.Invoke(e) End If End Sub Protected Overridable Sub AsyncPostWait(ByVal pwStruct As PostWaitStructure) If pwStruct.isValid() Then pwStruct.Callback.Invoke(pwStruct.State) pwStruct.WaitEvent.Set() End If End Sub Protected Class PostWaitStructure Public Callback As SendOrPostCallback Public State As Object Public WaitEvent As ManualResetEvent Public Sub New(ByVal SynchronizationCallback As SendOrPostCallback, ByVal Arg As Object, ByVal WaitHandle As ManualResetEvent) Callback = SynchronizationCallback State = Arg WaitEvent = WaitHandle End Sub End Class End Class
'Continuing MyClass from Above We Now need to support the Async Event Posting Public Class MyClass 'Naturally we would have the typical "On<Name>Event(<Typed>EventArgs) methods so lets put those in Protected Overridable Sub OnBeginExecute(ByVal e As CommandExecuteArgs) RaiseEvent BeginExecute(Me, e) End Sub Protected Overridable Sub OnParseError(ByVal e As CommandExecuteArgs) RaiseEvent ParseError(Me, e) End Sub Protected Overridable Sub OnCommandSuccess(ByVal e As CommandExecuteArgs) RaiseEvent CommandSuccess(Me, e) End Sub Protected Overridable Sub OnCommandError(ByVal e As CommandExecuteArgs) RaiseEvent CommandError(Me, e) End Sub Protected Overridable Sub OnCommandExecute(ByVal e As CommandExecuteArgs) RaiseEvent CommandExecute(Me, e) End Sub 'Next we need to add support for Asynchronous Posting (if necessary) Protected Overridable Sub OnCommandExecute(ByVal index As Integer, ByVal OriginalCommand As String, ByVal Command As SqlParser) If Me.IsAsync Then PostWaitEvent(AddressOf AsyncCommandExecute, new CommandExecuteArgs(index, Command.CommandType, OriginalCommand, Command.SQL, Command.Table, Command.LastError)) Else : OnCommandExecute(new CommandExecuteArgs(index, Command.CommandType, OriginalCommand, Command.SQL, Command.Table, Command.LastError)) End If End Sub Protected Overridable Sub OnCommandError(ByVal index As Integer, ByVal Exception As Exception, ByVal Cancelled As Boolean) If Me.IsAsync Then PostWaitEvent(AddressOf AsyncCommandError, new CommandExecuteArgs(index, Exception, Cancelled)) Else : OnCommandError(new CommandExecuteArgs(index, Exception, Cancelled)) End If Me.Cancel = CommandArgs.Cancel End Sub Protected Overridable Sub OnCommandSuccess(ByVal index As Integer) If Me.IsAsync Then PostWaitEvent(AddressOf AsyncCommandSuccess, new CommandExecuteArgs(index)) Else : OnCommandSuccess(new CommandExecuteArgs(index)) End If End Sub Protected Overridable Sub OnBeginExecute(ByVal CommandCount As Integer) If Me.IsAsync Then PostWaitEvent(AddressOf AsyncBeginExecute, new CommandExecuteArgs(CommandCount)) Else : OnBeginExecute(new CommandExecuteArgs(CommandCount)) End If End Sub 'But As you can see in the above I'm passing some Intermediate method to the PostWaitEvent() method so we have to add them 'THey are of SendOrPostCallBack delegate type for the AysncOperation.Post to post back to. Private Sub AsyncCommandExecute(ByVal Arg As Object) OnCommandExecute(DirectCast(Arg, CommandExecuteArgs)) End Sub Private Sub AsyncCommandError(ByVal Arg As Object) OnCommandError(DirectCast(Arg, CommandExecuteArgs)) End Sub Private Sub AsyncParseError(ByVal Arg As Object) OnParseError(DirectCast(Arg, CommandExecuteArgs)) End Sub Private Sub AsyncCommandSuccess(ByVal Arg As Object) OnCommandSuccess(DirectCast(Arg, CommandExecuteArgs)) End Sub End CLassGranted, my CommandExecuteArgs looks a little funky in there, some inaccurate constructor overloads, I am aware this will not directly compile on those issues. However, I don't want to past ALL of the code cause that would get confusing (especially the way I do code).
Regardless of my coding practices, if you follow the logic of the above object, you can see that the initial On<Blank>Event() methods simply call to a RaiseEvent with the provided EventArgs object. Below, we make each one more specific, passing parameters to the EventArgs object instead of the object itself. This allows our object to create the EventARgs and pass it out to the event in the best manner appropraite.
if We Did this:
Private WithEvents X as New MyClass()
X.Execute(Nothing)
private Sub X_BeginExecute(sender as object, e as CommandExecuteArg) Handles X.BeginExecute
End Sub
X is executing it's process on the Main UI thread, and the OnBeginExecute simply invokes the BeginExecute Event. Meanwhile any command After Execute is naturally block as synchronous ops do.
Private WithEvents X as New MyClass()
X.ExecuteAsync(Nothing)
private Sub X_BeginExecute(sender as object, e as CommandExecuteArg) Handles X.BeginExecute
End Sub
Now, When the ExecuteAsync fires off:
1. AsyncOperation is Create
2. Thread Is Create and Executed on the Internal ThreadProc Method
3. ThreadProc calls out to the Execute Method
4. Execute Calls the PerformOp Event (which should be implemented in the MyClass object above)
5. When In the PerformOp event of MyClass, the code (you/me) calls to the Exact same profile of OnBeginExecute() as was called during the Synchronous execution
6. OnBeginExecute reads IsAsync as True, and Calls PostWaitEvent wth the address of AsyncBeginExecute()
7. PostWaitEvent, creates a WaitHandle Event, then Posts the provided CallBack {AsyncBeginExecute}, Argument {CommandExecuteArgs} and WaitHandle Event in a new object back to the intermediate AsyncPostWait method.
8. AsyncPostWait method is now executing on the UI Thread. It calls to the provided Callback {AsyncBeginExecute} with the given ARgs {CommandExecuteARgs}.
9. AsyncBeginExecute Calls to OnBeginExecute which raises the event and voila.
Now you can eleminate a Stage or two, if you want to write this design each time you do async operations, but as I mentioned before I'm more of an Automation king, so I like to write a full model that handles this stuff, and eh, so what if i have a tiny overhead. when speed is a factor I alter my design for speed, but normally with what I do, it isn't noticeable.
Hopes this helps,
Jaeden "Sifo Dyas" al'Raec Ruiner
"Never Trust a computer. Your brain is smarter than any micro-chip."


