学习TPL(四)

引子

    现在要实现这样一个任务:

  • 下载一段内容(内容本身很小)
  • 这段内容在3个已知路径上存在,并且确认是完全相同的(也就是镜像)
  • 由于程序执行的网络环境不明,3个镜像的到达速度肯定有快慢

    于是,可以很简单的写出这样一段TPL的代码:

   1:  static void Test()
   2:  {
   3:      Task outer = Task.Factory.StartNew(() =>
   4:      {
   5:          Console.WriteLine("Outer started.");
   6:          CancellationTokenSource cts = new CancellationTokenSource();
   7:          CancellationToken token = cts.Token;
   8:          Task<string>[] tasks = new Task<string>[3];
   9:          tasks[0] = Task.Factory.StartNew(() => Download("http://www.cnblogs.com/vwxyzh/archive/2010/04/21/1716735.html", token), token);
  10:          tasks[1] = Task.Factory.StartNew(() => Download("http://www.cnblogs.com/vwxyzh/archive/2010/04/18/1714665.html", token), token);
  11:          tasks[2] = Task.Factory.StartNew(() => Download("http://www.cnblogs.com/vwxyzh/archive/2010/04/16/1713622.html", token), token);
  12:          int winnerIndex = Task.WaitAny(tasks);
  13:          cts.Cancel();
  14:          Console.WriteLine(tasks[winnerIndex].Result);
  15:      });
  16:  }
  17:   
  18:  static string Download(string url, CancellationToken token)
  19:  {
  20:      Thread.Sleep(1);
  21:      WebClient client = new WebClient();
  22:      ManualResetEvent mre = new ManualResetEvent(false);
  23:      DownloadStringCompletedEventArgs arg = null;
  24:      client.DownloadStringCompleted += (sender, e) =>
  25:          {
  26:              arg = e;
  27:              mre.Set();
  28:          };
  29:      token.Register(() => client.CancelAsync());
  30:      client.DownloadStringAsync(new Uri(url));
  31:      mre.WaitOne();
  32:      if (arg.Cancelled)
  33:          return null;
  34:      if (arg.Error != null)
  35:          throw arg.Error;
  36:      return arg.Result.Remove(100) + "...\r\nfrom " + url;
  37:  }

    在Main中执行Test方法可以看到:

Outer started.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="C...
from http://www.cnblogs.com/vwxyzh/archive/2010/04/18/1714665.html

    当然每次返回的网页并不一定都是这个,因为这里只是抓取了第一个返回的页面,其他页面的信息则被直接丢弃了。

    是不是看其来不错。

问题

    别开心的太早了,这个方式仅仅是看起来很美,如果把Main函数写成这样:

   1:  for (int i = 0; i < 100; i++)
   2:      Test();

    在跑一下,看看发生了什么:

Outer started.
Outer started.
Outer started.
Outer started.
Outer started.
Outer started.
Outer started.
Outer started.
Outer started.
Outer started.
Outer started.
Outer started.
Outer started.
Outer started.
Outer started.
Outer started.
Outer started.
Outer started.
Outer started.

    只看到了外面的父任务在不断的添加进来,而真正的Download过程反而一个都没跑!直到到达线程池的最大线程数,或者i的最大值。事实上,如果使用while(true)方式的话,只需要添加任务的速度略快于处理的数据,整个过程就必然会出现死锁(所有的线程池线程都在跑主任务,而这些主任务又都在等它们的子任务,但是,它们的子任务又在线程池排队)。

解决方案寻找中

    由于方法的外界对线程池的使用方式是这个方法所不知道的,所以任何依赖线程池的动作都有可能被某些极限情况造成死锁。所以,目前暂时还没想到好的解决方案,等待大家开动脑筋,发现解决之道。

[忘记发布了。。。]

原文地址:https://www.cnblogs.com/vwxyzh/p/1740858.html