Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Daniel Solis - Illustrated C# 2010 - 2010.pdf
Скачиваний:
16
Добавлен:
11.06.2015
Размер:
11.23 Mб
Скачать

CHAPTER 22 INTRODUCTION TO ASYNCHRONOUS PROGRAMMING

Processes, Threads, and Asynchronous Programming

In this chapter, we’re going to introduce four methods you can use to add multithreading to your programs. This chapter is a bit different from the previous chapters in that it goes beyond just the language features. Instead, we’ll also include classes from the BCL and include some programming techniques. In spite of the fact that these things are a bit beyond just the language features, I want to do this because it’s imperative that we as programmers increase our use of multiprocessing in our code— and I think a first book on C# is a good place to start.

When you start a program, the system creates a new process in memory. A process is the set of resources that comprise a running program. These include the virtual address space, file handles, and a host of other things required for the program to run.

Inside the process, the system creates a kernel object, called a thread, which represents the actual executing program. (Thread is short for “thread of execution.”) Once the process is set up, the system starts execution of the thread at the first statement in method Main.

Some important things to know about threads are the following:

By default, a process contains only a single thread, which executes from the beginning of the program to the end.

A thread can spawn other threads so that at any time, a process might have multiple threads in various states, executing different parts of the program.

If there are multiple threads in a process, they all share the process’s resources.

It’s threads, not processes, that are the units scheduled by the system for execution on the processor.

All the sample programs shown so far in this book have used only a single thread and have executed sequentially from the first statement in the program to the last. This is called synchronous programming. Asynchronous programming refers to programs that spawn multiple threads, which are, at least conceptually, executed at the same time. (They might not actually be executed at the same time.)

If the program is running on a multiprocessor system, the different threads might actually be executing at the same time on different processors. This can considerably improve performance, and as multicore processors become the norm, we need to write our programs to take advantage of this opportunity.

On a single-processor system, though, clearly only one instruction can be executed by the processor at a time. In this case, the operating system coordinates the threads so that the processor is shared among them. Each thread gets the processor for a short time, called a time slice, before being kicked off the processor and sent to the back of the line. This round-robin sharing of the processor lets all the threads work their ways through the code.

596

CHAPTER 22 INTRODUCTION TO ASYNCHRONOUS PROGRAMMING

Multithreading Considerations

Using multiple threads in a program, called multithreading, or just threading, creates program overhead and additional program complexity. Here are some examples:

There are time and resource costs in both creating and destroying threads.

The time required for scheduling threads, loading them onto the processor, and storing their states after each time slice is pure overhead.

Since the threads in a process all share the same resources and heap, it adds additional programming complexity to ensure that they’re not stepping on each other’s work.

Debugging multithreaded programs can be quite difficult, since the timing on each run of the program can be different, producing different results. And the act of running the program in a debugger blows the timing out of the water.

In spite of these considerations, the benefits of threading can outweigh its costs, as long as it’s used wisely—and not overused. For example, you’ve already seen that on a multiprocessor system, if the different threads can be placed on different processors, it can result in a much more efficient execution.

To help alleviate some of the costs associated with creating and destroying threads, the CLR maintains a thread pool for each process. Initially, a process’s thread pool is empty, but after a thread is created and used by a process and then the thread completes its execution, it isn’t destroyed but instead added to the process’s thread pool. Later, if the process needs another thread, the CLR recycles one from the pool, saving a significant amount of time.

Another common example where multithreading is crucial is in graphical user interface (GUI) programming, where users expect a quick response any time they click a button or use the keyboard. In this case, if the program needs to perform an operation that’s going to take any appreciable time, it should perform that operation on another thread, leaving the main thread available to respond to the user’s input. It would be totally unacceptable to have the program unresponsive during that time.

597

CHAPTER 22 INTRODUCTION TO ASYNCHRONOUS PROGRAMMING

The Complexity of Multithreading

Although multithreading is conceptually easy, getting all the details right can be frustratingly difficult on nontrivial programs. The areas that need to be considered are the following:

Communicating between the threads: There are few built-in mechanisms for communicating between threads, so this is often done simply using their shared memory, since the memory space is visible and accessible by all threads in the same process.

Coordinating threads: Although it’s easy to create threads, you also need to be able to coordinate their actions. For example, a thread might need to wait for one or more other threads to complete before it can continue its execution.

Synchronization of resource usage: Since all the threads in a process share the same resources and memory, you need to make sure that the different threads aren’t accessing and changing them at the same time, causing state inconsistencies.

The System.Threading namespace contains classes and types that you can use to build complex multithreaded systems. These include the Thread class itself and classes such as Mutex, Semaphore, and Monitor, which are used to synchronize resource usage. The use, complexities, and nuances of this tricky subject are beyond the scope of this text, and you’d be better advised to settle down with an in-depth book on the subject.

598

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]