哪个进程最好在线程和任务之间实现连续工作?

时间:2018-04-04 04:17:43

标签: c# multithreading task-parallel-library

我希望以下进程持续运行。但是混淆使用线程或任务。我也是实现Thread或Task的新手。我正在实现这个过程是对的吗?在线程和任务之间哪个更好地获得长时间运行过程的快速性能?

thin

此方法为每台机器创建线程。

private void BtnStart_Click(object sender, EventArgs e)
    {
        IClockThread();

    }

这是每个线程调用的另一种方法。

  public void IClockThread()
    {

        txtStatus.BeginInvoke((Action)(() => txtStatus.Text = "Thread for IClock Starts......" + Environment.NewLine));

        Thread[] ts = new Thread[IclockDetails.Count];
        for (int i = 0; i < 5; i++)
        {
            string IP = IclockDetails[i].IpAddress;
            ts[i] = new Thread(() =>
              {
                  ConnectMachineIClock(IP);
              });
            ts[i].Start();

        }
        txtStatus.BeginInvoke((Action)(() => txtStatus.Text += "Thread for IClock Ends............" + Environment.NewLine));
 }

2 个答案:

答案 0 :(得分:0)

分析

因此,您当前的代码基本上会为每个时钟细节创建一个新线程,以便基本上这些调用都是异步运行的。它没有等待任何线程完成。

第一个问题是创建新线程是昂贵的,并且在100个线程上运行100个任务很少(如果有的话)和4个并行运行的线程一样高效,因为它们变得可用时负责任务。

所以第一个问题是创建一堆新线程并不是最好的方法。

其次,使用100个线程(或者多个在IClockDetails中)来访问网络堆栈的效率也低于较少的并发线程。

任务和线程之间的区别

要直接回答您的第一个问题,最好在这种情况下使用任务。这也是你做的唯一区别

new Thread(() => {}).Start();

正在做

Task.Run(() => {});

这是第一种方式总是创建一个新线程(昂贵)来完成工作,而第二种方法可能会使用现有的空闲线程。

其次,一个Task总是创建一个后台线程,而不是前台,所以如果你的主线程和所有前台线程完成,任务的线程不会保持你的应用程序活着,而前台线程会。“ p>

就是这样。您的代码除了可能在现有空闲线程上运行,并且作为后台线程运行之外,没有任何区别。

标准解决方案

如前所述,创建新线程非常昂贵。因此,对您的问题的第一个简单回答是,将new Thread(() => {}).Start();来电更改为Task.Run(() => {});来更好。

public void IClockThread()
{
    txtStatus.BeginInvoke((Action)(() => txtStatus.Text = "Thread for IClock Starts......" + Environment.NewLine));

    for (int i = 0; i < IclockDetails.Length; i++)
        Task.Run(() => ConnectMachineIClock(IclockDetails[i].IpAddress));

    txtStatus.BeginInvoke((Action)(() => txtStatus.Text += "Thread for IClock Ends............" + Environment.NewLine));
}

我已经清理了代码,修复了for循环以使用实际的IclockDetails长度,删除了不必要的位,并用Task替换了线程。

现在,这将重新使用线程,并将它们作为后台线程运行。

与救援并行

如上所述,如果您的IclockDetails有100个项目或者超过20个项目,那么只为每个项目运行线程效率低下。相反,你可以将它们平行。

考虑一下

Parallel.For(0, 100, (i) =>
{
    Console.WriteLine($"I am {i} on thread {Thread.CurrentThread.ManagedThreadId}");
    Thread.Sleep(10 * i);
});

这将创建一系列方法,这些方法将在Partitioner认为合适的情况下并行调用。因此,这将基于其运行的系统的最佳线程数

运行

它只会向控制台输出i的工作项,它位于我指定的0100的范围之间。数字越大,延迟越多。因此,如果您现在运行此代码,您将看到一堆像这样的输出

I am 0 on thread 1
I am 1 on thread 1
I am 2 on thread 1
I am 5 on thread 3
I am 10 on thread 4
I am 3 on thread 1
I am 15 on thread 5
I am 6 on thread 3
I am 4 on thread 1
I am 20 on thread 6
I am 25 on thread 7
I am 11 on thread 4
I am 35 on thread 11
I am 8 on thread 1
I am 30 on thread 8
I am 45 on thread 12
I am 7 on thread 3
I am 40 on thread 10
I am 50 on thread 9
I am 55 on thread 14
I am 60 on thread 15
I am 16 on thread 13
I am 65 on thread 5
I am 70 on thread 16
I am 75 on thread 18
I am 9 on thread 1
...

正如您所看到的那样,运行顺序不同步,因为它们并行运行并且线程正在重新使用。

如果您愿意,可以限制一次运行的最大并行任务数

Parallel.For(0, 100, new ParallelOptions { MaxDegreeOfParallelism = 4 }...

在此示例中将其限制为4。但是,我个人会将其留给分区作出决定。

为了清楚理解,如果限制MaxDegreeOfParallelism = 1输出将是

I am 0 on thread 1
I am 1 on thread 1
I am 2 on thread 1
I am 3 on thread 1
I am 4 on thread 1
I am 5 on thread 1
I am 6 on thread 1
I am 7 on thread 1
I am 8 on thread 1
I am 9 on thread 1
I am 10 on thread 1
...
  

注意:Parallel.For调用未完成并继续执行下一行代码,直到完成所有工作。如果您想更改它,请执行Task.Run(() => Parallel.For(...));

最佳解决方案

因此,考虑到这一点,我对您的情况和最佳解决方案的建议是使用Parallel.For来分割您的工作并将其委托给正确的线程,并在系统看到&#39时重新使用这些线程适合。

另请注意,我只在此使用Task.Run来维护您的Thread for clock start/end输出,以便它们完全相同,并且在您完成工作之前返回IClockThread()方法,以便不更改您的当前的代码流。

public void IClockThread()
{

    txtStatus.BeginInvoke((Action)(() => txtStatus.Text = "Thread for IClock Starts......" + Environment.NewLine));

    Task.Run(() =>
    {
        Parallel.For(0, IclockDetails.Count, (i) =>
        {
            ConnectMachineIClock(IclockDetails[i].IpAddress);
        });
    });

    txtStatus.BeginInvoke((Action)(() => txtStatus.Text += "Thread for IClock Ends............" + Environment.NewLine));
}

答案 1 :(得分:-1)

Task和Thread之间的主要区别在于如何完成并发。

任务是并发,它被委托给应用程序的线程池中的线程。这个想法是一个Task是一个相当短暂的并发过程或函数,它被传递给线程池中的一个线程,在那里它被执行并完成,然后正在使用的线程返回给线程池以供其他一些线程使用。任务。

请参阅MSDN documentation Task Class概述,其中包含各种方法说明的链接等。

  

Task类表示不返回a的单个操作   值通常以异步方式执行。任务对象是一个   首先是基于任务的异步模式的核心组件   在.NET Framework 4中引入。因为工作由一个   任务对象通常在线程池线程上异步执行   而不是在主应用程序线程上同步,你可以使用   Status属性,以及IsCanceled,IsCompleted和   IsFaulted属性,用于确定任务的状态。最常见的,   lambda表达式用于指定任务的工作   执行。

     

对于返回值的操作,使用Task&lt; TResult&gt;类。

另请参阅Microsoft Docs Task-based Asynchronous Programming以及Task-based Asynchronous Pattern (TAP) in Microsoft Docs这是当前推荐的方法(有关多种模式,请参阅Asynchronous Programming Patterns for a discussion in Microsoft Docs)。

这篇MSDN文章异步编程描述了使用asyncawait关键字以及其他文章的链接,其中包括Microsoft Docs article Async in depth,这些文章可以了解详细信息。

线程是由应用程序创建的并发。这个想法是一个Thread是一个相当长寿,并发的过程或函数。

两者之间的一个主要区别是Task和Thread,它是一个Task被传递给一个准备好运行的线程,而Thread必须被创建并旋转。因此,任务的启动时间较短,启动效率较高,因为委托的线程已经存在。

在我看来,使用Task的主要原因是能够使短暂的动作或过程并发。因此,诸如访问网站或进行某种数据传输或执行需要几秒钟或更新数据存储的某种计算等事情是任务的理想活动类型。 .NET和C#以及C ++ / CLI都有大量的异步类型函数,它们可以与Task一起使用来触发活动,同时允许UI保持响应。

对于线程,诸如提供接受大量请求并对其执行操作的服务器或长时间监视多个设备或传感器的功能等活动将是线程的理想活动类型。其他活动将是额外的UI线程,以便处理复杂的用户界面或计算密集型任务,例如图形渲染,其吞吐量将通过能够使用一个或多个专用CPU核心来增强。

要记住的一点是,任务与线程不是二进制,无论是一个还是另一个决策。可以创建一个或多个线程来处理特定的,封装的和自给自足的功能,并且在创建的一个或多个线程内,任务构造可以用于处理线程的工作。一个常见的例子是UI线程,它被设计用于处理用户界面的各种消息,但是在UI线程内发生的特定动作由Task处理。另一个常见示例是使用Tasks处理多个并发连接的服务器线程。

Microsoft Docs文章The Managed Thread Pool有这样说:

  

有几种情况适合创建和   管理自己的线程而不是使用线程池线程:

     
      
  • 您需要一个前台线程。

  •   
  • 您需要一个具有特定优先级的线程。

  •   
  • 您的任务导致线程长时间阻塞。线程池有最大线程数,所以很大   被阻止的线程池线程数可能会阻止任务   开始。

  •   
  • 您需要将线程放入单线程单元中。所有ThreadPool线程都在多线程单元中。

  •   
  • 您需要具有与该线程关联的稳定标识,或将线程专用于任务。

  •   

另外,请参阅以下stackoverflow发布和有关Task和Thread之间差异的讨论。