Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why Thread.Join() DOES NOT hang my application when called on UI thread?

In my understanding (I am not good in threading), Join() blocks calling thread until thread on which Join() is called returns.

If that is true and Join() is called from UI thread, creating the new thread for some long running operation does not make any sense. There are questions on SO those ask why Join() hangs the application. It looks natural to me.

By the way, even it looks natural, my application does not behave accordingly. It does not hang my application.

Code without thread that hangs application: -

string retValue = "";
retValue = LongRunningHeavyFunction();
txtResult.Text = retValue;

Code with thread that DOES NOT hang application: -

string retValue = "";
Thread thread = new Thread(
() =>
{
    retValue = LongRunningHeavyFunction();
});
thread.Start();
thread.Join();
txtResult.Text = retValue;

Above code works great without hanging application. Function call takes around 15-20 seconds. Why application does not hang?

This is not a problem for me; actually it's a good news. But I just do not understand what difference it made? It does not match with what I read and learn.

I am using DotNet Framework 4.0 if that matters.

like image 248
Amit Joshi Avatar asked Aug 12 '16 06:08

Amit Joshi


1 Answers

The UI thread of your program created an STA, a single-threaded apartment, thanks to the [STAThread] attribute on your Main() entrypoint. Also review Thread.SetApartmentState(). STA is very friendly to code that is not thread-safe, often code that you cannot see because it is part of another program.

Like the code that gets involved in putting data on the clipboard, supply data in a Drag + Drop operation, shell extensions that get activated when you display an OpenFiledialog, controls like WebBrowser that need the client code to have a dispatcher so it can raise the DocumentCompleted event, windows hooks that listen for notifications, code that relies on the accessibility api to implement UI automation or a screen reader. Etcetera.

None of that code is required to be thread-safe, even though multiple threads in different processes are involved. Writing thread-safe code is difficult in general, it gets excessively difficult because there isn't any good way for the author to test it. He can't possibly test it against every possible program that might activate his code. Apartments were invented to solve this problem, removing the need to write thread-safe code.

STA is a promise you make. You swear, cross your heart-hope to die style, that your UI thread is well-behaved, it must have a dispatcher (aka pump a message loop) and must never block. The dispatcher is necessary to run code in a thread-safe way, it is the universal solution to the producer-consumer problem. And you must never block because doing so is quite likely to cause deadlock in the other programmer's code. The kind of deadlock you can never debug because you don't have the source code of that code. The UI thread of a Winforms, WPF or Modern UI program always implement this promise, the basic reason why the UI thread is different from any other thread you might use.

Enough introduction, the Thread.Join() call blocks and thus violates the STA contract. That's very bad so the CLR designers did something about it. They implemented Join() by not blocking in the case it is called on an STA thread, as you found out.

They did this by implementing a flavor of Application.DoEvents(). Which is a very notorious way to avoid a UI thread from becoming unresponsive. It can cause very difficult to diagnose re-entrancy bugs. Their version is not nearly as bad as DoEvents(), it is very selective about what kind of messages it allows to be dispatched to minimize the risk.

Which usually works out okay, it is however never something you want to intentionally test yourself. Re-entrancy bugs are very ratty bugs, about as bad as threading race bugs. You should therefore never use Thread.Join() on a UI thread. The .NET Framework gives excellent alternatives with the BackgroundWorker and Task classes, they both give you very good ways to execute code (whatever is after the Join() call) when the thread completes.

like image 129
Hans Passant Avatar answered Nov 04 '22 19:11

Hans Passant