//.net 的 HttpWebRequest 或 WebClient 在多线程情况下存在并发连接限制,这个限制在桌面操作系统如 windows xp , windows 7 下默认是2,在服务器操作系统上默认为10. 可用下面语句修改 System.Net.ServicePointManager.DefaultConnectionLimit = 512; String[] fileLines = listBox1.Items.Cast<string>().ToArray(); for (int i = 0; i < leng; i++) { string ss = fileLines[i]; string urld = UrlEncode(ss); var task2 = Task.Factory.StartNew(() => httpdown(urld, Path.GetFileName(fileLines[i]))); task1.Add(task2); }
httpdown的代码就是正常的httprequest下载文件的代码
private static string httpdown1(string url, string path) { // 设置参数 HttpWebRequest request = null; HttpWebResponse response = null; Stream responseStream = null; Stream stream = null; try { request = WebRequest.Create(url) as HttpWebRequest; //发送请求并获取相应回应数据 response = request.GetResponse() as HttpWebResponse; //直到request.GetResponse()程序才开始向目标网页发送Post请求 responseStream = response.GetResponseStream(); //创建本地文件写入流 stream = new FileStream(path, FileMode.Create); long totalBytes = 0; try { long totalBytes1 = response.ContentLength; totalBytes = totalBytes1; //获取文件总大小 } catch { errorfile = url; } lock (new object()) { maxalready = maxalready + 1; maxlen = maxlen + (int)totalBytes; } byte[] bArr = new byte[2048]; int size = responseStream.Read(bArr, 0, (int)bArr.Length); while (size > 0) { stream.Write(bArr, 0, size); size = responseStream.Read(bArr, 0, (int)bArr.Length); if (size > 0) { lock (new object()) { nowlen = nowlen + (int)size; } } } stream.Close(); responseStream.Close(); lock (new object()) { downfile1 = downfile1 + "|"+Path.GetFileName(url); downcomplete = downcomplete + 1; } return path; } catch (System.Exception ex) { string strEx = ""; strEx = string.Format("sendhttp Exception:{1},{0}", url, ex.Message.ToString()); System.Threading.Thread.Sleep(3000); errorfile = url; return "Exception:" + strEx; } finally { if (stream != null) { try { stream.Close(); stream = null; } catch (System.Exception e1) { Console.Write(e1); } } if (responseStream != null) { try { responseStream.Close(); responseStream = null; } catch (System.Exception e1) { Console.Write(e1); } } if (response != null) { try { response.Close(); response = null; } catch (System.Exception e1) { Console.Write(e1); } } if (request != null) { try { request.Abort(); request = null; } catch (System.Exception e1) { Console.Write(e1); } } } }
大致的意思就是一次性用task把全部的List列表中的文件都下载下来
本人这个代码在本机使用没有发现什么问题
然后放到虚拟机和客户机里就发现经常只下载一部分列表文件
而且还不固定,有时候只下几个,有时候只下十个
但是本机则一直都是完整可以下载的
原本以为可能是httprequest并发的问题
System.Net.ServicePointManager.DefaultConnectionLimit = 512;
这句加上去也没有效果还是这样的情况.
尝试调试了一下,虚拟机和客户机没办法装VS调试,用输出Log的方式调试了
发现是 var task2 = Task.Factory.StartNew(() => httpdown(urld, Path.GetFileName(fileLines[i])));
这一句是都有调用的
但是实际上httpdown并不是每一个task都会开始
所以就想问一下高手们,这是什么原因?
解决方案
1
lock (new object()) 这有什么意义?
你所说的“但是实际上httpdown并不是每一个task都会开始”,那就先别用 Task 了呗。
你所说的“但是实际上httpdown并不是每一个task都会开始”,那就先别用 Task 了呗。
1
另外,for 循环中的变量 i 使用到 StartNew(…..) 中到底有没有错误,以及多余地在 Finally 部分写一大堆没有必要的 Close语句等等,以及其它许多代码,看着都很混乱。你需要仔细测试。并且你的环境可能对质量要求不高,以至于代码倾向于花哨凌乱。
10
Task.Factory.StartNew(method)
只是
var th = new Thread(method);
th.Start();
的简写形式(可能新能也会有所提高)
但线程能否立刻就开始,这是说不定的。
你的代码只是开启了线程,并没有看到监视 httpdown 结束后返回状态的代码
也没有看到 httpdown 处理失败时的补救措施
而网路是个复杂的系统,任何人都不能保证他会正确的响应每一个请求
因此丢失(或是说没有下载到)部分文件,是很正常的事情
只是
var th = new Thread(method);
th.Start();
的简写形式(可能新能也会有所提高)
但线程能否立刻就开始,这是说不定的。
你的代码只是开启了线程,并没有看到监视 httpdown 结束后返回状态的代码
也没有看到 httpdown 处理失败时的补救措施
而网路是个复杂的系统,任何人都不能保证他会正确的响应每一个请求
因此丢失(或是说没有下载到)部分文件,是很正常的事情
10
加不加 TaskCreationOptions.LongRunning 都无所谓
加了 TaskCreationOptions.LongRunning 只是告诉 Task 待执行的代码可能执行时间较长
这样在 Task 调度线程时就不去考虑重用该线程,也就是 10 句 Task.Factory.StartNew() 就开 10 个线程
不加时,可能会只开 5 个线程。你可以通过 Thread.CurrentThread.ManagedThreadId 观察到线程被复用的现象
其实,httpdown 被顺序执行了 10 次,还是被 10 个线程分别执行了 1 次。都不是问题的所在
问题在于 httpdown 的执行是可能失败的(网络原因、对方服务器的原因甚至本人机器的原因),所以你必须要检查 httpdown 的返回结果,来决定下一步的行动。例如你的 httpdown 就可能是:
return path;
return “Exception:” + strEx;
假如返回的是 “Exception:” + strEx,都下载失败了,那你怎么可以认为是下载成功了呢?
加了 TaskCreationOptions.LongRunning 只是告诉 Task 待执行的代码可能执行时间较长
这样在 Task 调度线程时就不去考虑重用该线程,也就是 10 句 Task.Factory.StartNew() 就开 10 个线程
不加时,可能会只开 5 个线程。你可以通过 Thread.CurrentThread.ManagedThreadId 观察到线程被复用的现象
其实,httpdown 被顺序执行了 10 次,还是被 10 个线程分别执行了 1 次。都不是问题的所在
问题在于 httpdown 的执行是可能失败的(网络原因、对方服务器的原因甚至本人机器的原因),所以你必须要检查 httpdown 的返回结果,来决定下一步的行动。例如你的 httpdown 就可能是:
return path;
return “Exception:” + strEx;
假如返回的是 “Exception:” + strEx,都下载失败了,那你怎么可以认为是下载成功了呢?
5
Task肯定会运行,只不过要等线程池有多余线程,你只要多等一段时间就行了
10
发现一个问题
for (int i = 0; i < leng; i++)
{
string ss = fileLines[i];
string urld = UrlEncode(ss);
var task2 = Task.Factory.StartNew(() => httpdown(urld, Path.GetFileName(fileLines[i])));
task1.Add(task2);
}
你这样传值对吗?
for (int i = 0; i < leng; i++)
{
string ss = fileLines[i];
string urld = UrlEncode(ss);
var task2 = Task.Factory.StartNew(() => httpdown(urld, Path.GetFileName(fileLines[i])));
task1.Add(task2);
}
你这样传值对吗?
10
var task2 = Task.Factory.StartNew((object name) => httpdown(urld, (string)name), Path.GetFileName(fileLines[i]));
要这样才对吧?
要这样才对吧?
5
照道理,应该多等一会就好了。
5
try catch 用这么多,真的好吗?为什么不是主动检测非得用触发异常机制呢!
5
要知道系统的运行原理,并不是你开多少线程就会同时运行。每个cpu单核只能同时运行一个线程,通过切换线程上下文来实现并行运算的。并不是说你开N个线程就一定会更快执行完毕,跟服务器性能也是有关的
10
没有 TaskCreationOptions.LongRunning
var a = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; for (var i = 0; i < 10; i++) { var p = i; var t = Task.Factory.StartNew( () => { var tid = Thread.CurrentThread.ManagedThreadId; Console.WriteLine("线程号:{0} 任务号:{1}", tid, p); }); } Console.ReadKey();
有 TaskCreationOptions.LongRunning
var a = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; for (var i = 0; i < 10; i++) { var p = i; var t = Task.Factory.StartNew( () => { var tid = Thread.CurrentThread.ManagedThreadId; Console.WriteLine("线程号:{0} 任务号:{1}", tid, p); }, TaskCreationOptions.LongRunning); } Console.ReadKey();
可以清楚的看到:没有 TaskCreationOptions.LongRunning 参数时,线程是可以被复用的(一个任务完成后,这个线程还可去完成其他任务)
而有 TaskCreationOptions.LongRunning 时,每个任务都占用一个线程
5
本人认为,你能否优化一下你的提供下载文件的远程的服务器,最好使用微软IIS提供的http下载服务器。
5
private Thread Thread_BackGroud = null; private void Start() { Thread_BackGroud_For = new Thread(new ThreadStart(Run)); Thread_BackGroud_For.IsBackground = true;//注意,要用后台线程。 Thread_BackGroud_For.Start(); } private void Run() { ThreadPool.SetMaxThreads(3, 3);//注意这里,不要设置太大,先设置为3,看看能否正常。 Task t1 = Task.Factory.StartNew(() => { RunParallelFor(); }); Task.WaitAll(t1); t1.Dispose(); t1 = null; } private void RunParallelFor() { Parallel.For(0, leng, i => { AssignTaskForDown(fileLines[i]); }); } private void AssignTaskForDown(string _str)//_str是远程的http下载路径及文件名 { try { WebClient client = new WebClient();//建议用这个,不要用WebRequest client.DownloadFile(_str, pathDest);//开始下载,这个pathDest,是下载后放置的路径和文件名。 client.Dispose(); } catch () { } }
你试一试上面的思路,看看能否能有效解决问题。需要注意的地方都写在里面了。
5
Thread_BackGroud_For = new Thread(new ThreadStart(Run)); Thread_BackGroud_For.IsBackground = true;//注意,要用后台线程。 Thread_BackGroud_For.Start(); 这里笔误,应该是: Thread_BackGroud = new Thread(new ThreadStart(Run)); Thread_BackGroud.IsBackground = true;//注意,要用后台线程。 Thread_BackGroud.Start();
13
其实你这样就可以看到究竟是哪里的问题了
var task2 = Task.Factory.StartNew(() => { Console.WriteLine( httpdown(urld, Path.GetFileName(fileLines[i])) ); });
task 只是个线程调度工具