Groups | Blog | Home
all groups > c# > march 2008 >

c# : Anyone from Microsoft confirm this bug or explain this as feature



Aamir Mahmood
3/20/2008 11:54:18 PM
I have posted this issue earlier on
microsoft.public.dotnet.languages.csharp, but nobody replied to that.

Consider following code:

--------------------------------------------------------------------------------

private void button1_Click(object sender, EventArgs e) {
using (System.Transactions.TransactionScope tx = new TransactionScope())
{
System.Transactions.Transaction.Current.TransactionCompleted += new
TransactionCompletedEventHandler(Current_TransactionCompleted);

System.Console.WriteLine(System.Threading.Thread.CurrentThread.GetHashCode());
// prints 4, could be different on your machine
throw new Exception();

tx.Commit();
}
}

private void Current_TransactionCompleted(object sender,
TransactionEventArgs e) {
System.Console.WriteLine(System.Threading.Thread.CurrentThread.GetHashCode());
// prints 6, could be different on your machine
}

--------------------------------------------------------------------------------

The problem is the output, first it outputs 4, and it ouputs 6 (it can be a
different output on your machine).

I have various fields with [ThreadStatic] attribute. Since the
TransactionCompleted is coming on a different thread, it is making all those
[ThreadStatic] fields useless.
This is driving me crazy.

The event MUST be fired on the same thread that created TransactionScope and
subscribed this event.
But the Microsoft implemented in a way, that the initial thread is stopped
and a different thread is used to fire the event (WHY???). After the event
handler function finishes, application continues with the original thread.
WHY EVENT IS FIRED ON A DIFFERENT THREAD? I have [ThreadStatic] members
which I cannot use.
I have cleanup routines which were tied to that specific transactions
created for that specific thread.

Since this is not explained in any documentation, I think it is a bug.

Thanks.

AM.

Jon Skeet [C# MVP]
3/21/2008 8:40:47 AM
[quoted text, click to view]

<snip>

[quoted text, click to view]

Then you've got a design issue - it doesn't mean it's a bug.

[quoted text, click to view]

Well, it must for your code to work. I can't immediately see anything
in the documentation suggesting that that should happen though.

[quoted text, click to view]

Where is the guarantee to that it *will* occur in the same thread? If
there's no guarantee, you're relying on unspecified behaviour, which
you assumed would be the behaviour you wanted.

What would you expect to happen if you'd called BeginCommit from a
thread which wasn't running a message pump? How would you expect it to
marshall back to that thread?

--
Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet
Cowboy (Gregory A. Beamer)
3/21/2008 11:54:25 AM
I was not going to add anything here, as Jon has already provided a long
answer, but it started nagging on me, so I apologize, in advance.

First, let's understand bug and design flaw. Suppose I create a library that
creates a binary array out of a string. As I am working with English only, I
design this to return a single byte per character in the string. You,
working in another language, find that it is not giving you enough fidelity
to reproduce your string on the other end. So you call and say I have a bug.

My library does what I said it would do, so it is not a bug. If my library
turned the word fudge into the binary representation of turds, it would be a
bug, as it is not working as I stated it would work.

It could, however, be a design flaw, as it only works with ASCII values. The
question of whether or not it is a design flaw depends on stated intent. For
example, "creates a binary representation of an ASCII string" would not
indicate a design flaw, while "creates a binary representation of any
string" would.

-----------

Next, I see the statement
[quoted text, click to view]

So, you have a cleanup dependent not ONLY on the transaction, but on the
particular thread. This means your cleanup is actually work that is part of
the transaction, as it has a very tightly coupled dependency on more than
the work being done, but the actual manner in which it is completed. In
other words, the TransactionScope is not really what you are concerned with,
but the thread created to complete the TransactionScope.

If you ask me, it is a design flaw. It might be Microsoft's design flaw, as
they have not included a way to create a transaction for a transaction and
its cleanup. But, as you have the dependency, I would also argue that you
have a design flaw in your own code, as the TransactionScope is acting
exactly as published.

Let's, however, focus on it being a bug that Microsoft needs to fix. If so,
it will likely take weeks, at minimum, before we see a patch of any sort,
and months before a fully regression tested patch. Can you wait this long
before you fix the problem? If yes, then get on connect and log the bug. If
not, I would find a way to eliminate the thread dependency in your code. At
this point, working code is probably better than being right.

--
Gregory A. Beamer
MVP, MCP: +I, SE, SD, DBA

Subscribe to my blog
http://gregorybeamer.spaces.live.com/lists/feed.rss

or just read it:
http://gregorybeamer.spaces.live.com/

*************************************************
| Think outside the box!
|
*************************************************
[quoted text, click to view]

Peter Ritchie [C# MVP]
3/25/2008 12:46:00 PM
The TransactionCompleted event is raised on the thread on which the
transaction is running. This is very common for events, if thread affinity
isn't documented then you can't assume it will occur on any particular
thread. It's your applications requirement that the event handle execute on
a given thread. It's your application's responsibility to marshal the call
to another thread, if needed.

Given that a Form cannot have it's form data accessed by any other thread,
why are you using the ThreadStatic attribute on fields on a Form class anyway?

--
Browse http://connect.microsoft.com/VisualStudio/feedback/ and vote.
http://www.peterRitchie.com/blog/
Microsoft MVP, Visual Developer - Visual C#


[quoted text, click to view]
Ben Voigt [C++ MVP]
3/25/2008 5:00:36 PM
[quoted text, click to view]

The data associated with a Form can be accessed from any thread, what makes
you think otherwise? Only operations that require sending windows messages
to the HWND have thread affinity.

Peter Ritchie [C# MVP]
3/25/2008 5:52:10 PM
"Form data" as in controls. It's actually a combination of windows messages
and the fact that the main (GUI) thread must be STA that cause cross-thread
control access exceptions.

But, the documentation for Control.InvokeRequired details it quite well:
"Controls in Windows Forms are bound to a specific thread and are not thread
safe. Therefore, if you are calling a control's method from a different
thread, you must use one of the control's invoke methods to marshal the call
to the proper thread."

There's certain cases where you don't have to Invoke; but it's difficult to
track down which will and which won't case an exception and usually just
easier to marshal all access to the form's thread.

--
Browse http://connect.microsoft.com/VisualStudio/feedback/ and vote.
http://www.peterRitchie.com/blog/
Microsoft MVP, Visual Developer - Visual C#


[quoted text, click to view]
Peter Duniho
3/25/2008 6:19:01 PM
On Tue, 25 Mar 2008 17:52:10 -0700, Peter Ritchie [C# MVP]
[quoted text, click to view]

It seems to me that "form data" is not naturally inferred to mean simply
"controls". My guess is that that's the point of disagreement between you
and Ben. I think he rightly took issue with the over-general statement
about what requires the use of Invoke().

As you know, a Form-derived class can and often will have a variety of
additional data items added by the programmer in that class, and there's
no fundamental restriction regarding access to that data. Likewise your
own methods you might add to the class. In fact, if it were otherwise, it
would be impossible to write code such as MSDN recommends, where the same
method invokes itself in order to get onto the main thread. You wouldn't
be able to call that method in the first place.

It's true that most of the properties and methods in the Form class itself
must be called from the thread that owns that instance of the Form. But
other than that, any data or other members in a Form-derived class are
under no such restriction. This includes any data the person writing the
inheriting class might add, as well as any internal (private) data the
Form class (and its ancestors) might themselves define (though I have no
idea what that data might be, I'd be surprised if there isn't at least a
little internal data not directly related to the unmanaged resources used
by a Form).

Of course, all of the above applies to any class inheriting Control, not
just forms. It's just that this happened to come up in the context of a
comment specifically about a form.

[quoted text, click to view]

It's not really that difficult. The MSDN documentation lists the specific
members that can be called without Invoke(), and of course anything you
might add to a Form-derived class, you should be able to determine
yourself whether it requires the use of Invoke(). In fact, there's no
fundamental reason why _any_ member added in a derived class would
_require_ the use of Invoke(), though it can often be the simplest
technique to deal with synchronization to do so anyway.

Peter Ritchie [C# MVP]
3/25/2008 6:41:02 PM
[quoted text, click to view]

Quite possibly.

<snip>
[quoted text, click to view]

Yes, and that's why I asked about the OP's use of ThreadStatic. If many (or
even just some) of the form/control's methods/properties need to be marshaled
to a specific thread, having thread-specific fields seems like a really bad
AddThis Social Bookmark Button