Code Bye

Task批量运行的问题,望高手解答

 //.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 了呗。

1

另外,for 循环中的变量 i 使用到 StartNew(…..) 中到底有没有错误,以及多余地在 Finally 部分写一大堆没有必要的 Close语句等等,以及其它许多代码,看着都很混乱。你需要仔细测试。并且你的环境可能对质量要求不高,以至于代码倾向于花哨凌乱。

10

Task.Factory.StartNew(method)
只是
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,都下载失败了,那你怎么可以认为是下载成功了呢?

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);
}
你这样传值对吗?

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 只是个线程调度工具


CodeBye 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权 , 转载请注明Task批量运行的问题,望高手解答