 |
|
 |
A bit short for an article, but clearly written and interesting.
Just because the code works, it doesn't mean that it is good code.
|
|
|
|
 |
|
 |
I like the idea you have here, but its just too much to sustain, and too much code for something this simple.
There are two really good alternatives, create a generic method that handles the invoke (I don't prefer this). and the REAL solution to all your problems, Its called a user created code snippet. I have defined a similar pattern
void mymethod(type arg)
{
if (InvokeRequired)
{
this.Invoke((Action)delegate { mymethod(arg);});
}
else
{
}
}
something like that. in a couple different forms in my code snippets. Then in Visual Studio I just hit Ctrl +k, X. and then a list of my snippets appear, I click the Invoke pattern I need and the hightlight jumps to a change region usually the method name. and after a couple keystrokes and a tab I'm on to a bigger picture.
Not to say this isn't a good idea. But what I would like to see is the Control class expanded to have a propery called propertyInvoke that exposes all the properties hand handles the invoke required automatically. like textBox1.propertyInvoke.visible = false;
Til then, I'm very content with my code snippets, I have an entire library of them I've created using Snippet Compiler.
As always, thanks for sharing code.
|
|
|
|
 |
|
 |
I don't really use this method anymore, it ended up being pretty brittle for some of the reasons you've listed. What I tend to do now is an MVP model for my winforms and use this helper class to execute threaded stuff which takes care of forcing the UI code onto the main thread so I don't need to worry about it at all in my form code.
A common class for executing tasks with a responsive UI[^]
|
|
|
|
 |
|
 |
I like the way done with Action stuff! and it's quite clear.
|
|
|
|
 |
|
 |
I spent a long time searching for something like this on the Google.
I adapted it to my purpose, for it to be more generic... Atleast I think it is anyway.
private bool removeInvoke(Delegate target)
{
return removeInvoke(target, null);
}
private bool removeInvoke(Delegate target, object[] funcParams)
{
if (this.InvokeRequired)
{
if (funcParams != null)
this.BeginInvoke(target, funcParams);
else
this.BeginInvoke(target);
return true;
}
return false;
}
private delegate void delegate_function(string param1, string param2);
private void function(string param1, string param2)
{
if (removeInvoke(new delegate_function(function), new object[] { param1, param2})) return;
...
}
hmmm pie
modified on Sunday, April 5, 2009 9:32 PM
|
|
|
|
 |
|
 |
I really like the way you handle this.
I was getting tired of dealing with invokes or Synchronization contexts.
I just have one comment to make.
In my case, I have a form, and a Custom control 'A' that is actually my UI (because I want to be able to load the UI from other places like InternetExplorer for example)
The UI use a custom control 'B' (among others) that uses threads to update its content (which was a source of much frustrations until I found your solution)
The 'problem' I have is that for your solution to work, the custom control 'B' itself (not the form) must be created by your 'AOPFactory.Create<T>'
Problem with that is that the control is created in InitializeComponent which is generated automatically each time 'A' is modified.
So every time I have to go in the 'InitializeComponent' and modify the line where 'B' is create to actually use 'AOPFactory.Create<B>'.
It's no big deal. But it's annoying.
Unfortunatly, I don't think there is really a work around for this.
I just wanted to make that comment for other people who might encounter the same problem.
|
|
|
|
 |
|
 |
One more thing.
Somehow it seems that now I cannot open my UI in the Desginer.
I get the following error:
Method 'Osherove.SimpleInterception.AOPFactory.Create[CustomControlA]' not found.
at System.RuntimeType.InvokeMember(String name, BindingFlags bindingFlags, Binder binder, Object target, Object[] providedArgs, ParameterModifier[] modifiers, CultureInfo culture, String[] namedParams)
at System.ComponentModel.Design.Serialization.CodeDomSerializerBase.DeserializeExpression(IDesignerSerializationManager manager, String name, CodeExpression expression)
at System.ComponentModel.Design.Serialization.CodeDomSerializer.DeserializeStatementToInstance(IDesignerSerializationManager manager, CodeStatement statement)
at System.ComponentModel.Design.Serialization.CodeDomSerializer.Deserialize(IDesignerSerializationManager manager, Object codeObject)
at System.Windows.Forms.Design.ControlCodeDomSerializer.Deserialize(IDesignerSerializationManager manager, Object codeObject)
at System.ComponentModel.Design.Serialization.TypeCodeDomSerializer.DeserializeName(IDesignerSerializationManager manager, String name, CodeStatementCollection statements)
Once again not a really big deal.
I just have to replace temporarily the creation of the custom control with the 'normal' way to do it, then back again to your way.
|
|
|
|
 |
|
 |
Another problem.
If you have a function like the following:
[RunInUIThread]
public virtual void SetContent(CustomClass customClass)
{
...
}
And you call it passing a null argument (which I have to do from time to time for some reason), MethodInterceptor.GetArgumentNames will crash at the following command:
sb.Append(argument.GetType().Name);
Because 'argument' is actually null.
A simple fix is to test if invocation.Arguments is null or not like you do with
'if (invocation.GenericArguments!=null)'
|
|
|
|
 |
|
 |
I don't really use this method anymore, it ended up being pretty brittle for some of the reasons you've listed. What I tend to do now is an MVP model for my winforms and use this helper class to execute threaded stuff which takes care of forcing the UI code onto the main thread so I don't need to worry about it at all in my form code.
A common class for executing tasks with a responsive UI[^]
|
|
|
|
 |
|
 |
Hi,
This is excellent.
Just in case you need an alternative without attribute because you do strange things with the CAB, then this is an alternative without attribute : invokerequiredinvoke-easier
Hope this help.
Manitra.
==
System.Threading.Thread.Sleep(999999);
Great !
|
|
|
|
 |
|
|
 |
|
 |
I was totally delighted when I saw you article... and than totally stunned when I saw how it works under the hood.
(sign) Well, I guess I was expecting too much... at least I see that I'm not the only one who would like to mark method with some attribute and have some common logic (logging in another ql option) executed before further method call.
Requirement to explicitly create proxy object killed all magic
|
|
|
|
 |
|
|
 |
|
 |
Very nice indeed, but this approach may lead to performance degradation because of the use of reflection for attributes discovery behind the scene.
Though for most of UI apps out there it won't be an issue, and for those who do hardcore multithreading checking for InvokeRequired isn't normally an issue.
|
|
|
|
 |
|
 |
When you are talking about UI stuff, reflection on an attribute isn't that big of a deal. Methods not marked as virtual won't be overloaded via AOP magic and have the overhead so I don't think there is going to be any performance degradation to speak of. Feel free to write a sample proving the performance degradation.
|
|
|
|
 |
|
 |
When you have bunch of background threads submitting updates to UI this becomes a bottleneck, because UI update happens in sync manner and the background threads would have to wait...
But as I said in my previous message it isn't a case in majority of apps out there.
|
|
|
|
 |
|
 |
I don't immediately see how that would be the case.. Maybe I'll write a test with some sleeps in it and see if that occurs. Thanks for the tip.
|
|
|
|
 |
|
 |
I setup some tests to verify the behavior you were talking about and it does indeed block, i.e. 4 threaded calls to a method that does a sleep 1000, takes 4 seconds, not 1. However, the menual method also does this, so I can only asssume this is happening because we are forcing all of them on the same thread. Are you saying this won't happen when using the normal invokerequired method but will in the AOP case? It seems to happen in both cases for me. Here are the two testing methods I was using, I can't modify the article anymore apparently.
///
/// Test for a lengthy operation on the ui thread, will sleep for a second and
/// see if 4 running at same time takes 1 second or 4 seconds
///
[RunInUIThread]
protected virtual void LengthyOperationAOP()
{
Thread.Sleep(1000);
//after running 4 times, close form
lock (LengthyOperationLock)
{
if (++LengthyOperationRunTimes >= LengthyOperationTimesToRun)
this.Close();
}
}
///
/// Test for a lengthy operation on the ui thread, will sleep for a second and
/// see if 4 running at same time takes 1 second or 4 seconds
///
protected void LengthyOperationManual()
{
if (this.InvokeRequired)
{
// Pass the same function to BeginInvoke, but the call would come on the correct thread and InvokeRequired will be false.
this.BeginInvoke(new DoThreadedGoodManualType(LengthyOperationManual));
return;
}
Thread.Sleep(1000);
//after running 4 times, close form
lock (LengthyOperationLock)
{
if (++LengthyOperationRunTimes >= LengthyOperationTimesToRun)
this.Close();
}
}
|
|
|
|
 |
|
 |
Awesome 1: Elegant, saves me a lot of time
Awesome 2: I never would have thought you could use method attributes like this!
|
|
|
|
 |
|
 |
There isn't any magic in the attributes, the magic is in the form creation, which is putting a layer on top of the form and gives us an opportunity to intercept the method calls. We could just as easily put in config entries designating all the method names that we wanted this to happen for.
|
|
|
|
 |
|
 |
Great, IU love the simplicity. No more useless delegates and if (invokeRequired) constructs... :P
|
|
|
|
 |
|
 |
Hey Paul,
I like the somplicity.
What about handling parameters?
|
|
|
|
 |
|
 |
That's what's great, parameters are fine. I don't have any parameters to the functions in this example but you don't have to worry about that.
|
|
|
|
 |
|
 |
One thing I forgot to mention in the article - you'll notice the "virtual" keyword on the method. The way the AOP method interception stuff works is by inheriting from your class and overriding the function to call a Before function, the normal function and then calling an After function. So there are a few extra setup steps in order to use this, including creating the form a slightly different way but overall I think this is much better than the standard way.
|
|
|
|
 |