第九篇 C#多线程

1 线程基础

主要学习Windows和CLR如何协同提供一个线程处理架构。

1.1 使用专用线程

1)线程需要以非普通线程优先级运行。

2)需要线程作为一个foreground thread,放置应用程序在线程结束任务前终止。

3) The compute-bound task is extremely long-running;

4) 可能调用Thread的Abort方法来提前终止它。

为了创建专用线程,要构造System.Threading.Thread类的实例。

public sealed class Thread : CriticalFinalizerObject, ... {
    public Thread(ParameterizedThreadStart start);
    // Less commonly used constructors are not shown here
}

start方法参数标识专用线程要执行的方法,该方法必须和 ParameterizedThreadStart委托的签名匹配。

delegate void ParameterizedThreadStart(Object obj);

1.2 线程调度和优先级

注1:抢占式操作系统必须使用算法判断在什么时候调用哪些线程多长时间。

注2:每个线程的内核对象都包含一个上下文结构,上下文反应了线程上一次执行完毕后CPU寄存器的状态。

系统将进程的优先级类和其中的一个线程的相对优先级映射为一个优先级(0~31)。

1.3 Foreground Threads 和 Background Threads

CLR将每个线程要么视为Foreground Threads,要么视为Background Threads。一个进程的所有前台线程停止运行时,CLR强制终止仍在运行的任何后台线程。这些后台线程被直接终止,不抛出异常。

在线程的生存期,任何时候都可以从前台线程变成后台线程,或者从后台线程变为前台线程。IsBackground字段来设置。

应用程序的主线程以及通过构造一个Thread对象来显示创建的任何线程都默认前台线程,而线程池线程默认为后台线程。另外,由进入托管执行环境的本机代码创建的任何线程都标记为后台线程。

2 asynchronous compute-bound operation

2.1 CLR线程池基础

线程池作为程序拥有的可用线程的集合,每个CLR有一个线程池,这个线程池由CLR控制的所有AppDoMain共享。如果一个进程中加载了多个CLR,那个每个CLR都有它自己的线程池。

CLR初始化时,线程池中没有线程。在内部,线程池维护了一个操作请求队列,应用程序执行一个异步操作时,就调用某个方法,将一个记录项追加到线程池的队列中。线程池的代码从这个队列中提取记录项,将这个记录项派发给一个线程池线程。

要将一个asynchronous compute-bound operation添加到线程池,可以使用下面的典型方法:

static Boolean QueueUserWorkItem(WaitCallback callBack);
static Boolean QueueUserWorkItem(WaitCallback callBack, Object state);

The callback method you write must match the System.Threading.WaitCallback delegate type, which is defined as follows:

delegate void WaitCallback(Object state);

2.2 上下文

执行上下文数据结构包括:

1)安全设置: 压缩栈、Thread的Principal属性和windows身份。

2)宿主设置:see System.Threading.HostExecutionContextManager。

3)逻辑调用上下文数据:see System.Runtime.Remoting.Messaging.CallContext’s LogicalSetData and LogicalGetData methods。

每当一个线程(初始线程)使用另一个线程(辅助线程)执行任务时,前者执行上下文应该流向(复制到)辅助线程。

在System.Threading namespace有一个ExecutionContext类,允许控制一个线程的执行上下文如何从一个线程流向另一个线程。like:

public sealed class ExecutionContext : IDisposable, ISerializable {
    [SecurityCritical] public static AsyncFlowControl SuppressFlow();
    public static void RestoreFlow();
    public static Boolean IsFlowSuppressed();
    // Less commonly used methods are not shown
}

2.3 任务

任务:ThreadPool的QueueUserWorkItem没办法知道操作在什么时候完成,也没有机制在操作完成时获得返回值。任务可以解决这一问题。

可以通过System.Threading.Tasks命名空间中的类型来使用任务。

ThreadPool.QueueUserWorkItem(ComputeBoundOp, 5); // Calling QueueUserWorkItem
new Task(ComputeBoundOp, 5).Start(); // Equivalent of above using Task
Task.Run(() => ComputeBoundOp(5)); // Another equivalent

可以通过向构造器传递一些TaskCreationOptions标志来控制Task执行方式。

public enum TaskCreationOptions {
    None = 0x0000,// The default
    // Hints to the TaskScheduler that you want this task to run sooner than later.
    PreferFairness = 0x0001,
    // Hints to the TaskScheduler that it should more aggressively create thread pool threads.
    LongRunning = 0x0002,
    // Always honored: Associates a Task with its parent Task (discussed shortly)
    AttachedToParent = 0x0004,
    // If a task attempts to attach to this parent task, it is a normal task, not a child task.DenyChildAttach = 0x0008,
    // Forces child tasks to use the default scheduler as opposed to the parent’s scheduler.
    HideScheduler = 0x0010
}

任务的功能有:

1)等待任务完成并获取结果。

2)取消任务。

3)任务完成时自动启动新任务。

4)任务可以启动子任务。

2.4 Parallel的静态For,ForEach和Invoke方法。

这个static System.Threading.Tasks.Parallel类可以通过任务提升性能,他内部使用Task对象。

1)ForEach方法

// One thread performs all this work sequentially
for (Int32 i = 0; i < 1000; i++) DoWork(i);

you can instead get multiple thread pool threads to assist in performing this work by using the Parallel class’s For method:

// The thread pool’s threads process the work in parallel
Parallel.For(0, 1000, i => DoWork(i));

Similarly, if you have a collection, instead of doing this:

// One thread performs all this work sequentially
foreach (var item in collection) DoWork(item);

you can do this:

// The thread pool's threads process the work in parallel
Parallel.ForEach(collection, item => DoWork(item));

2) Invoke方法

And finally, if you have several methods that you need to execute, you could execute them all sequentially, like this:

// One thread executes all the methods sequentially
Method1();
Method2();
Method3();

or you could execute them in parallel, like this:

// The thread pool’s threads execute the methods in parallel
Parallel.Invoke(
() => Method1(),
() => Method2(),
() => Method3());

3 I/O限制的异步操作

注:各种I/O操作的结果还是要由线程池线程来处理。

3.1 Windows如何执行I/O操作

1)执行同步I/O操作

例如Read方法,会阻塞线程。

2)执行异步I/O操作

例如改为调用ReadAsync,ReadAsync内部分配一个Task对象来代表用于完成读取操作的代码。线程不在阻塞,认识立即从ReadAsync调用里返回。但是数据可能没处理好,所以不能在ReadAsync之后的代码中访问传递的Byte[]中的字节。

注意,调用ReadAsync返回的是一个Task对象,可以在对象上调用ContinueWith来登记任务完成时执行的回调方法,然后在回调方法中处理数据。当然,也可以利用c#的异步函数功能简化编码,一顺序方式写代码(感觉就像是执行同步I/O)。

3.2 C#异步函数

执行异步操作是构建可伸缩的,响应灵敏的应用程序的关键,它允许使用少量线程执行大量操作。这可通过Task和称为异步函数的一个C#语言功能。

private static async Task IssueClientRequestAsync(String serverName, String message) {
    using (var pipe = new NamedPipeClientStream(serverName, "PipeName", PipeDirection.InOut,
    PipeOptions.Asynchronous | PipeOptions.WriteThrough)) {

        pipe.Connect(); // Must Connect before setting ReadMode
        pipe.ReadMode = PipeTransmissionMode.Message;

        // Asynchronously send data to the server
        Byte[] request = Encoding.UTF8.GetBytes(message);
        await pipe.WriteAsync(request, 0, request.Length);

        // Asynchronously read the server's response
        Byte[] response = new Byte[1000];
        Int32 bytesRead = await pipe.ReadAsync(response, 0, response.Length);
        return Encoding.UTF8.GetString(response, 0, bytesRead);
    } // Close the pipe
}

你可能感兴趣的:(C#-学习笔记)