Thread in C# - Part 2: Creating and Starting Threads  
 

(Post 05/10/2007) Threads are created using the Thread class’s constructor, passing in a ThreadStart delegate – indicating the method where execution should begin. Here’s how the ThreadStart delegate is defined:

public delegate void ThreadStart();

Calling Start on the thread then sets it running. The thread continues until its method returns, at which point the thread ends. Here’s an example, using the expanded C# syntax for creating a TheadStart delegate:

class ThreadTest {
static void Main() {
Thread t = new Thread (new ThreadStart (Go));
t.Start(); // Run Go() on the new thread.
Go(); // Simultaneously run Go() in the main thread.
}
static void Go() { Console.WriteLine ("hello!"); }

In this example, thread t executes Go() – at (much) the same time the main thread calls Go(). The result is two near-instant hellos:

A thread can be created more conveniently using C#'s shortcut syntax for instantiating delegates:

static void Main() {
Thread t = new Thread (Go); // No need to explicitly use ThreadStart
t.Start();
...
}
static void Go() { ... }

In this case, a ThreadStart delegate is inferred automatically by the compiler. Another shortcut is to use an anonymous method to start the thread:

static void Main() {
Thread t = new Thread (delegate() { Console.WriteLine ("Hello!"); });
t.Start();
}

A thread has an IsAlive property that returns true after its Start() method has been called, up until the thread ends.

A thread, once ended, cannot be re-started.

Passing Data to ThreadStart

Let’s say, in the example above, we wanted to better distinguish the output from each thread, perhaps by having one of the threads write in upper case. We could achieve this by passing a flag to the Go method: but then we couldn’t use the ThreadStart delegate because it doesn’t accept arguments. Fortunately, the .NET framework defines another version of the delegate called ParameterizedThreadStart, which accepts a single object argument as follows:

public delegate void ParameterizedThreadStart (object obj);

The previous example then looks like this:

class ThreadTest {
static void Main() {
Thread t = new Thread (Go);
t.Start (true); // == Go (true)
Go (false);
}
static void Go (object upperCase) {
bool upper = (bool) upperCase;
Console.WriteLine (upper ? "HELLO!" : "hello!");

}

In this example, the compiler automatically infers a ParameterizedThreadStart delegate because the Go method accepts a single object argument. We could just as well have written:

Thread t = new Thread (new ParameterizedThreadStart (Go));
t.Start (true);

A feature of using ParameterizedThreadStart is that we must cast the object argument to the desired type (in this case bool) before use. Also, there is only a single-argument version of this delegate.

An alternative is to use an anonymous method to call an ordinary method as follows:

static void Main() {
Thread t = new Thread (delegate() { WriteText ("Hello"); });
t.Start();
}
static void WriteText (string text) { Console.WriteLine (text); }

The advantage is that the target method (in this case WriteText) can accept any number of arguments, and no casting is required. However one must take into account the outer-variable semantics of anonymous methods, as is apparent in the following example:

static void Main() {
string text = "Before";
Thread t = new Thread (delegate() { WriteText (text); });
text = "After";
t.Start();
}
static void WriteText (string text) { Console.WriteLine (text); }


Anonymous methods open the grotesque possibility of unintended interaction via outer variables if they are modified by either party subsequent to the thread starting. Intended interaction (usually via fields) is generally considered more than enough! Outer variables are best treated as ready-only once thread execution has begun – unless one's willing to implement appropriate locking semantics on both sides.

Another common system for passing data to a thread is by giving Thread an instance method rather than a static method. The instance object’s properties can then tell the thread what to do, as in the following rewrite of the original example:

class ThreadTest {
bool upper;

static void Main() {
ThreadTest instance1 = new ThreadTest();
instance1.upper = true;
Thread t = new Thread (instance1.Go);
t.Start();
ThreadTest instance2 = new ThreadTest();
instance2.Go(); // Main thread – runs with upper=false
}

void Go() { Console.WriteLine (upper ? "HELLO!" : "hello!"); }

Naming Threads

A thread can be named via its Name property. This is of great benefit in debugging: as well as being able to Console.WriteLine a thread’s name, Microsoft Visual Studio picks up a thread’s name and displays it in the Debug Location toolbar. A thread’s name can be set at any time – but only once – attempts to subsequently change it will throw an exception.

The application’s main thread can also be assigned a name – in the following example the main thread is accessed via the CurrentThread static property:

class ThreadNaming {
static void Main() {
Thread.CurrentThread.Name = "main";
Thread worker = new Thread (Go);
worker.Name = "worker";
worker.Start();
Go();
}
static void Go() {
Console.WriteLine ("Hello from " + Thread.CurrentThread.Name);
}
}

Foreground and Background Threads

By default, threads are foreground threads, meaning they keep the application alive for as long as any one of them is running. C# also supports background threads, which don’t keep the application alive on their own – terminating immediately once all foreground threads have ended.

Changing a thread from foreground to background doesn’t change its priority or status within the CPU scheduler in any way.

A thread's IsBackground property controls its background status, as in the following example:

class PriorityTest {
static void Main (string[] args) {
Thread worker = new Thread (delegate() { Console.ReadLine(); });
if (args.Length > 0) worker.IsBackground = true;
worker.Start();
}
}

If the program is called with no arguments, the worker thread runs in its default foreground mode, and will wait on the ReadLine statement, waiting for the user to hit Enter. Meanwhile, the main thread exits, but the application keeps running because a foreground thread is still alive.

If on the other hand an argument is passed to Main(), the worker is assigned background status, and the program exits almost immediately as the main thread ends – terminating the ReadLine.

When a background thread terminates in this manner, any finally blocks are circumvented. As circumventing finally code is generally undesirable, it's good practice to explicitly wait for any background worker threads to finish before exiting an application – perhaps with a timeout (this is achieved by calling Thread.Join). If for some reason a renegade worker thread never finishes, one can then attempt to abort it, and if that fails, abandon the thread, allowing it to die with the process (logging the conundrum at this stage would also make sense!)

Having worker threads as background threads can then beneficial, for the very reason that it's always possible to have the last say when it comes to ending the application. Consider the alternative – foreground thread that won't die – preventing the application from exiting. An abandoned foreground worker thread is particularly insidious with a Windows Forms application, because the application will appear to exit when the main thread ends (at least to the user) but its process will remain running. In the Windows Task Manager, it will have disappeared from the Applications tab, although its executable filename still be visible in the Processes tab. Unless the user explicitly locates and ends the task, it will continue to consume resources and perhaps prevent a new instance of the application from starting or functioning properly.

A common cause for an application failing to exit properly is the presence of “forgotten” foregrounds threads.

Thread Priority

A thread’s Priority property determines how much execution time it gets relative to other active threads in the same process, on the following scale:

enum ThreadPriority { Lowest, BelowNormal, Normal, AboveNormal, Highest }

This becomes relevant only when multiple threads are simultaneously active.

Setting a thread’s priority to high doesn’t mean it can perform real-time work, because it’s still limited by the application’s process priority. To perform real-time work, the Process class in System.Diagnostics must also be used to elevate the process priority as follows (I didn't tell you how to do this):

Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;

ProcessPriorityClass.High is actually one notch short of the highest process priority: Realtime. Setting one's process priority to Realtime instructs the operating system that you never want your process to be preempted. If your program enters an accidental infinite loop you can expect even the operating system to be locked out. Nothing short of the power button will rescue you! For this reason, High is generally considered the highest usable process priority.

If the real-time application has a user interface, it can be undesirable to elevate the process priority because screen updates will be given excessive CPU time – slowing the entire computer, particularly if the UI is complex. (Although at the time of writing, the Internet telephony program Skype gets away with doing just this, perhaps because its UI is fairly simple). Lowering the main thread’s priority – in conjunction with raising the process’s priority – ensures the real-time thread doesn’t get preempted by screen redraws, but doesn’t prevent the computer from slowing, because the operating system will still allocate excessive CPU to the process as a whole. The ideal solution is to have the real-time work and user interface in separate processes (with different priorities), communicating via Remoting or shared memory. Shared memory requires P/Invoking the Win32 API (web-search CreateFileMapping and MapViewOfFile).

Exception Handling

Any try/catch/finally blocks in scope when a thread is created are of no relevance once the thread starts executing. Consider the following program:

public static void Main() {
try {
new Thread (Go).Start();
}
catch (Exception ex) {
// We'll never get here!
Console.WriteLine ("Exception!");
}

static void Go() { throw null; }
}

The try/catch statement in this example is effectively useless, and the newly created thread will be encumbered with an unhandled NullReferenceException. This behavior makes sense when you consider a thread has an independent execution path. The remedy is for thread entry methods to have their own exception handlers:

public static void Main() {
new Thread (Go).Start();
}

static void Go() {
try {
...
throw null; // this exception will get caught below
...
}
catch (Exception ex) {
Typically log the exception, and/or signal another thread
that we've come unstuck
...
}

From .NET 2.0 onwards, an unhandled exception on any thread shuts down the whole application, meaning ignoring the exception is generally not an option. Hence a try/catch block is required in every thread entry method – at least in production applications – in order to avoid unwanted application shutdown in case of an unhandled exception. This can be somewhat cumbersome – particularly for Windows Forms programmers, who commonly use the "global" exception handler, as follows:

using System;
using System.Threading;
using System.Windows.Forms;

static class Program {
static void Main() {
Application.ThreadException += HandleError;
Application.Run (new MainForm());
}

static void HandleError (object sender, ThreadExceptionEventArgs e) {
Log exception, then either exit the app or continue...
}
}

The Application.ThreadException event fires when an exception is thrown from code that was ultimately called as a result of a Windows message (for example, a keyboard, mouse or "paint" message) – in short, nearly all code in a typical Windows Forms application. While this works perfectly, it lulls one into a false sense of security – that all exceptions will be caught by the central exception handler. Exceptions thrown on worker threads are a good example of exceptions not caught by Application.ThreadException (the code inside the Main method is another – including the main form's constructor, which executes before the Windows message loop begins).

The .NET framework provides a lower-level event for global exception handling: AppDomain.UnhandledException. This event fires when there's an unhandled exception in any thread, and in any type of application (with or without a user interface). However, while it offers a good last-resort mechanism for logging untrapped exceptions, it provides no means of preventing the application from shutting down – and no means to suppress the .NET unhandled exception dialog.

In production applications, explicit exception handling is required on all thread entry methods. One can cut the work by using a wrapper or helper class to perform the job, such as BackgroundWorker (discussed in Part 3).

(Sưu tầm)


 
 

 
     
 
Công nghệ khác:


Thread in C# - Part 1: Overview and ConceptsXử lý những sự cố máy tính xấu nhất
Phòng và diệt virus USB một cách đơn giảnTạo trình duyệt hoàn hảo
Tăng tuổi thọ máy tính bằng nâng cấpXử lý “Back” cho AJAX
  Xem tiếp    
 
Lịch khai giảng của hệ thống
 
Ngày
Giờ
T.Tâm
TP Hồ Chí Minh
Hà Nội
 
   
New ADSE - Nhấn vào để xem chi tiết
Mừng Sinh Nhật Lần Thứ 20 FPT-APTECH
Nhấn vào để xem chi tiết
Bảng Vàng Thành Tích Sinh Viên FPT APTECH - Nhấn vào để xem chi tiết
Cập nhật công nghệ miễn phí cho tất cả cựu sinh viên APTECH toàn quốc
Tiết Thực Vì Cộng Đồng
Hội Thảo CNTT
Những khoảnh khắc không phai của Thầy Trò FPT-APTECH Ngày 20-11