My and threading (TheadStatic, ThreadSafeObjectProvider)
Fri, Apr 14 2006 1:50
Okay, My.Forms and other properties of My are thread static, but what exactly does that mean ?
Thread static as the name might imply, means that the field is thread specific. Each thread will have it's own copy of the field. This is incredibly useful when dealing with transactional types of data, and it's also useful with workflows etc.
In .NET, you make a field thread static by applying, the ThreadStatic attribute to it. e.g.
<ThreadStatic>Private m_Context as New Object
or at least that's the theory. However it isn't that simple. If you tried using a thread static field as shown, you'd find that the field would only be initialized on the thread that called the instance constructor; for all other threads it would be null. So the pattern you should use is a property with a private backing field. The property should initialize the field if needed. This ensures correct initialization for every thread. You don't have to worry about locks, as the backing field is thread specific. So the simplest form of adding a thread safe and specific variable is as follows:
<ThreadStatic>Private m_Context as Object
Public ReadOnly Property MyContext as Object
If m_Context Is Nothing Then m_Context = New Object
But I stress that's the simplest form. When dealing with remoting, class libraries running on web services etc, you might decide to use the http context to provide that "per transaction" kind of storage. At this point it really does get to be a lot of work for what should be a simple thing…… Enter My to the rescue
As part of the My framework, the VB.NET 2005 compiler generates an ThreadSafeObjectProvider(Of T) class for you. This is used throughout the My Application framework. It's generated with your application because the actual context it uses is based on the type of application you are building, be it a UI app (console or winforms), a class library or a web app. VB deals with all the complexities of the plumbing. To use the ThreadSafeObjectProvider class you write code like :
Private m_MyContext As New My.MyProject.ThreadSafeObjectProvider(Of Object)
Private ReadOnly Property MyContext () As Object
And there you have a nice thread safe, thread specific storage. I used "Object" above, but you can make it anything you like. I used this earlier today to provide a temporary cache across methods inside a component. The cache had to be thread specific as it was being used in a sorting routine, and we definitely didn't want to be doing locks during the sort. Of course, astute readers of my blog are probably saying, "hey wouldn't captures and anonymous delegates
given you the same effect", and the answer would have been probably yes ;) albeit at the expense of code reuse.
Okay, so that's the good news. The bad news is the ThreadSafeObjectProvider is marked as not browsable, so you won't get intellisense when you go to add it to your code. That's a real pain. In fact still having to write that backing field and property declaration is a pain, so I created my own snippet that will give you the above pattern, and assigned it the shortcut of "ThreadStatic". It's a good memory trigger that should I go to write ThreadStatic and hit the tab key, I'll be blessed with a nice safe wrapper rather than what can be risky business if done badly.
Okay, so we've looked a little at ThreadStatic and ThreadSafeObjectProvider. Now onto My and in particular Tobin's blog entry
The my application framework generates classes for the My.<dot> experience. My.Application is actually an instance of the MyApplication class. Likewise My.Forms is an instance of the MyForms class. These instances are actually exposed via properties in the MyProject module. And yes they are exposed using the very same pattern as I showed above and that's in my ThreadStatic snippet
So what's that mean ? Well it means that My.Forms is thread specific. One could argue they are being a bit overzealous there, but considering the very nature of Windows.Forms is STA threaded, and that you have to marshal over to the UI thread when trying to set properties of controls, then it's probably more consistent being thread specific.
But how do you deal with Forms across different threads ? Tobin put forward one workaround of using the open forms collection and passing in a string. I always see flashing red lights around string literals in code that isn't automatically generated, so I would not suggest you do that. Instead you can easily create a shared property that exposes what the MyForms main thread's instance. Using a partial class for your MyApplication, just hook into the application startup, and store a reference to My.Forms, then expose that:
Partial Friend Class MyApplication
Private Shared m_forms As MyForms
Public ReadOnly Property SharedForms() As MyForms
Private Sub MyApplication_Startup(ByVal sender As Object , ByVal e As Microsoft.VisualBasic.ApplicationServices.StartupEventArgs) Handles Me.Startup
m_forms = My.Forms
Now throughout your code you could access My.Application.SharedForms.Form1 etc, and that wouldn't be thread specific. Of course, in the "problem" code Tobin showed, that would just mean you were in fact dealing with the same form, but on the wrong thread, and so you'd get a threading exception. But life odes get a little simpler because you'd now be able to test if Invoke was required. Using the sample code from Tobin's blog, the Ui thread safe code becomes:
Public Sub WaitForData(ByVal strMessage As String)
Dim frm As Form2 = My.Application.SharedForms.Form2
If frm.InvokeRequired Then
frm.Invoke(New UpdateTextHandler(AddressOf WaitForData), strMessage)
'if here we're on the Ui thread
frm.TextBox1.Text &= strMessage
So there we've used the Shared forms to see if invoke is required, and if so we call Control.Invoke back to the same method.