多线程为什么简单的多任务不起作用?

时间:2016-12-13 11:22:30

标签: c# multithreading task-parallel-library task multitasking

var finalList = new List<string>();
var list = new List<int> {1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ................. 999999};

var init = 0;
var limitPerThread = 5;

var countDownEvent = new CountdownEvent(list.Count);

for (var i = 0; i < list.Count; i++)
{
    var listToFilter = list.Skip(init).Take(limitPerThread).ToList();
    new Thread(delegate()
                   {
                       Foo(listToFilter);
                       countDownEvent.Signal();
                   }).Start();    
    init += limitPerThread;
}

//wait all to finish
countDownEvent.Wait();


private static void Foo(List<int> listToFilter)
{
    var listDone = Boo(listToFilter);
    lock (Object)
    {
        finalList.AddRange(listDone);
    }
}

这不是:

var taskList = new List<Task>();

for (var i = 0; i < list.Count; i++)
{
    var listToFilter = list.Skip(init).Take(limitPerThread).ToList();
    var task = Task.Factory.StartNew(() => Foo(listToFilter)); 
    taskList.add(task);   
    init += limitPerThread;
}

//wait all to finish
Task.WaitAll(taskList.ToArray());

此过程最终必须至少创建700个线程。当我使用Thread运行时,它可以工作并创建所有这些。但是对于Task,它没有..它似乎没有启动倍数Tasks异步。

我真的想知道为什么......任何想法?

修改

PLINQ 的另一个版本(如建议的那样)。

var taskList = new List<Task>(list.Count);
Parallel.ForEach(taskList, t =>
                   {
                       var listToFilter = list.Skip(init).Take(limitPerThread).ToList();
                       Foo(listToFilter);
                       init += limitPerThread;
                       t.Start();
                   });
Task.WaitAll(taskList.ToArray());

EDIT2:

public static List<Communication> Foo(List<Dispositive> listToPing)
{
    var listResult = new List<Communication>();
    foreach (var item in listToPing)
    {
        var listIps = item.listIps;
        var communication = new Communication
        {
            IdDispositive = item.Id
        };

        try
        {
            for (var i = 0; i < listIps.Count(); i++)
            {
                var oPing = new Ping().Send(listIps.ElementAt(i).IpAddress, 10000);
                if (oPing != null)
                {
                    if (oPing.Status.Equals(IPStatus.TimedOut) && listIps.Count() > i+1)
                        continue;
                    if (oPing.Status.Equals(IPStatus.TimedOut))
                    {
                        communication.Result = "NOK"; 
                        break;
                    }
                    communication.Result = oPing.Status.Equals(IPStatus.Success) ? "OK" : "NOK";
                    break;
                }
                if (listIps.Count() > i+1)
                    continue;
                communication.Result = "NOK";
                break;
            }
        }
        catch
        {
            communication.Result = "NOK";
        }
        finally
        {
            listResult.Add(communication);
        }
    }

    return listResult;
}

3 个答案:

答案 0 :(得分:6)

Task不是多线程的。它们可以用于此目的,但实际上它们实际上用于相反的 - 在单个线程上进行多路复用。

要使用多线程任务,我建议使用Parallel LINQ。它已经有很多优化,例如列表的智能分区,只产生与CPU核心一样多的线程等。

要理解Taskasync,请以这种方式考虑它 - 典型的工作负载通常包括需要等待的IO。也许您阅读文件,查询Web服务,或访问数据库等等。重点是 - 你的线程会等待一段时间(至少在CPU周期内),直到你从一些遥远的目的地得到回应。

在Olden Days™中,这意味着您的线程被锁定(暂停)直到该响应出现。如果你想在此期间做其他事情,你需要生成一个新线程。这是可行的,但效率不高。每个OS线程都带有很大的开销(内存,内核资源)。你最终可能会有几个线程主动烧毁CPU,这意味着操作系统需要在它们之间切换,这样每个线程都会占用一些CPU时间,这些“上下文切换”非常昂贵。

async更改了该工作流程。现在,您可以在同一个线程上执行多个工作负载。虽然一项工作是await来自远方来源的结果,但另一项工作可以介入并使用该线程来做其他有用的事情。当第二个工作负载到达自己的await时,第一个工作负载可以唤醒并继续。

毕竟,生成比CPU内核更多的线程是没有意义的。你不会以这种方式完成更多的工作。恰恰相反 - 更多时间将用于切换线程,并且可用于有用工作的时间更少。

这就是最初为Task / async / await设计的内容。然而,并行LINQ也利用它并将其重用于多线程。在这种情况下,您可以这样看待 - 其他线程是您的主线程是您的主线程正在等待的“遥远的目的地”。

答案 1 :(得分:5)

任务在线程池上执行。这意味着少数线程将服务于大量任务。您有多线程,但不是每个生成的任务的线程。

你应该使用任务。您的目标应该是使用与CPU一样多的线程。通常,线程池正在为您执行此操作。

答案 2 :(得分:0)

你是如何衡量表现的?您是否认为700线程比700线程执行的4任务更快?不,他们不会。

  

好像它没有启动多次任务异步

你是怎么想到的?正如其他人在评论和其他答案中所建议的那样,您可能需要删除线程创建,因为在创建700线程后,您将降低系统性能,因为您的线程会在处理器时间内相互争斗,而不会任何工作都做得更快。

因此,您需要为async/await方法添加Foo的IO操作,并使用SendPingAsync版本。此外,您的方法可以简化,因为listIps.Count() > i + 1条件的许多检查都是无用的 - 您可以在for条件块中执行此操作:

public static async Task<List<Communication>> Foo(List<Dispositive> listToPing)
{
    var listResult = new List<Communication>();
    foreach (var item in listToPing)
    {
        var listIps = item.listIps;
        var communication = new Communication
        {
            IdDispositive = item.Id
        };

        try
        {
            var ping = new Ping();
            communication.Result = "NOK";

            for (var i = 0; i < listIps.Count(); i++)
            {
                var oPing = await ping.SendPingAsync(listIps.ElementAt(i).IpAddress, 10000);
                if (oPing != null)
                {
                    if (oPing.Status.Equals(IPStatus.Success)
                    {
                        communication.Result = "OK";
                        break;
                    }
                }
            }
        }
        catch
        {
            communication.Result = "NOK";
        }
        finally
        {
            listResult.Add(communication);
        }
    }

    return listResult;
}

您的代码的其他问题是PLINQ版本不是线程安全的:

init += limitPerThread;

并行执行时可能会失败。您可以引入一些帮助方法,例如this answer

private async Task<List<PingReply>> PingAsync(List<Communication> theListOfIPs)
{
    Ping pingSender = new Ping();
    var tasks = theListOfIPs.Select(ip => pingSender.SendPingAsync(ip, 10000));
    var results = await Task.WhenAll(tasks);

    return results.ToList();
}

执行此类检查(为简单起见,try/catch逻辑已删除):

public static async Task<List<Communication>> Foo(List<Dispositive> listToPing)
{
    var listResult = new List<Communication>();
    foreach (var item in listToPing)
    {
        var listIps = item.listIps;
        var communication = new Communication
        {
            IdDispositive = item.Id
        };
        var check = await PingAsync(listIps);
        communication.Result = check.Any(p => p.Status.Equals(IPStatus.Success)) ? "OK" : "NOK";
     }
 }

您可能应该使用Task.Run而不是Task.StartNew来确保您没有阻止UI线程。