第一章 管理程序流(In .net4.5) 之 实现多线程和异步处理

时间:2021-10-26 21:44:08

1. 概述

  本章主要讲解.net4.5如何实现多线程和异步处理的相关内容。

2. 主要内容

  2.1 理解线程

      ① 使用Thread类

  public static class Program
  {
      public static void ThreadMethod()
      {
          for (int i = ; i < ; i++)
          {
              Console.WriteLine(“ThreadProc: {}”, i);
              Thread.Sleep();
          }
      }
      public static void Main()
      {
          Thread t = new Thread(new ThreadStart(ThreadMethod));
          t.Start();
          for (int i = ; i < ; i++)
          {
              Console.WriteLine(“Main thread: Do some work.”);
              Thread.Sleep();
          }
          t.Join();
      }
  }

      *Thread.Join()方法的调用是通知主线程等待,直到其他线程执行完毕。

      *Thread.Sleep(0)是标记当前线程的状态为完成,这样系统就可以立即切换到其他线程了。

      ② 如果某线程的IsBackground属性设置为true,主程序退出前将不会考虑该线程是否还在执行。

      ③ 使用ParameterizedThreadStart委托,可以通过线程的start方法传递数据。

      ④ 用ThreadStatic Attribute 标记一个属性,每个使用该属性的线程都会获得一个该属性的副本。

      ⑤ 使用ThreadLocal<T>类,你可以定义线程本地数据,并且为每一个线程单独初始化。

public static class Program
{
public static ThreadLocal<int> _field =
new TheadLocal<int>(() =>
{
return Thread.CurrentThread.ManagedThreadId;
}); public static void Main()
{
new Thread(() =>
{
for (int x=; x < _field.value; x++)
Console.WriteLine("Thread A: {0}", x);
}).Start(); new Thread(() =>
{
for (int x=; x < _field.value; x++)
Console.WriteLine("Thread B: {0}", x);
}).Start(); Console.ReadKey();
}
}

      ⑥ 线程池

        提供线程的管理和重用。Web服务器接收请求,就是一个使用线程池的典型的例子。

  2.2 使用Task

      使用Task类,可以知道线程的完成状态以及接收返回值。

      ① 使用ContinueWith方法,可以实现任务完成之后的后续任务。

Task<int> t = Task.Run(() => { return ; });
t.ContinueWith(() => { Console.WriteLine("Canceled");},
TaskContinationOptions.OnlyOnCanceled);
t.ContinueWith(() => { Console.WriteLine("Faulted");},
TaskContinationOptions.OnlyOnFaulted);

      ② Task还可以嵌套子任务。

      ③ TaskFactory可以批量管理Task。

  2.3 使用Parallel类

    Parallel类有一对儿静态方法For、ForEach和Invoke方法,可以用这些来实现并行任务。

Parallel.For(, ,  i =>
{
Thread.Sleep();
}); var numbers = Enumerable.Range(, );
Parallel.ForEach(numbers, i =>
{
Thread.Sleep();
});

    使用ParallelLoopState可以实现跳出循环(break())或者终止程序(Stop());

  2.4 使用 async 和 await

    这两个关键字用于简化异步代码逻辑。

    用async标记的方法,就具备了可以被划分指定部分到多个线程去执行的功能。具体划分哪些部分,用await关键字来标记。

public static async Task<string> DownloadContent()
{
using (HttpClient client = new HttpClient())
{
string result = await client.GetStringAsync("http://www.z.cn");
return result;
}
}

      不需要ui交互的时候,可以调用ConfigureAwait方法禁用SynchronizationContext,可以获得更好的体验。

* 标记了async的方法,里面要有await。

          * 标记了async的方法,不要返回void。(async事件除外)

  2.5 使用 Parallel Language Integrated Query (PLINQ)

    使用PLINQ,可以将一个顺序查询转化为并行版本。

var numbers = Enumerable.Range(, );
var parallelResult = numbers.AsParallel()
.Where(i => i % == )
.ToArray();

    使用AsOrdered操作,可以保证结果是有序的。

    .net framework把并行过程中的异常都集中到了AggregateException 中。

  2.6 使用并行集合(concurrent collections)

    .net framework提供了一些线程安全的集合:

    ① BlockingCollection<T>

      删除数据时会阻塞程序,添加数据比较快。

      可以使用CompleteAdding方法通知其他被阻塞的线程。

public static void Main()
{
BlockingCollection<string> col = new BlockingCollection<string>();
Task read = Task.Run(() =>
{
foreach(string v in col.GetConsumingEnumerable())
Console.WriteLine(v);
}); Task write = Task.Run(() =>
{
while(true)
{
string s = Console.ReadLine();
if (string.IsNullOrWhiteSpace(s)) break;
col.Add(s);
}
}); write.wait();
}

    ② ConcurrentBag<T>

      用副本方式支持并发。无序。

      TryPeek方法在多线程中不太有用。peek过程中可能数据已经被修改了。

ConcurrentBag<int> bag = new ConcurrentBag<int>();
Task.Run(() =>
{
bag.Add();
Thread.Sleep();
bag.Add();
});
Task.Run(() =>
{
foreach(int i in bag)
Console.WriteLine(i);
}).wait();

      *上面的程序仅打印42。因为向bag添加21的时候,打印程序已经执行完毕了。

    ③ ConcurrentQueue<T> 和 ConcurrentStack<T>

      也是用快照方式实现。

    ④ ConcurrentDictionary

var dict = new ConcurrentDictionary<string, int>();
if (dict.TryAdd("k1", ))
Console.WriteLine("Added"); if (dict.TryUpdate("k1", , ))
Console.WriteLine("42 updated to 21"); dict["k1"] = ; //Overwhrite uncondictionally int r1 = dict.AddOrUpdate("k1", , (s, i) => i * );
int r2 = dict.GetOrAdd("k2", );

3. 总结

  ① 可以把每个线程当成一个独享cpu的程序。

  ② 建议使用ThreadPool来管理线程。

  ③ Task是对一个执行逻辑的封装。推荐用于多线程代码中。

  ④ Paralle用于代码中的并行操作。

  ⑤ PLINQ是LINQ的扩展,用于并行查询。

  ⑥ 用 async 和 await,可以用同步的形式编写异步的代码。

  ⑦ 并行集合可以线程安全的用于多线程环境中。