【问题标题】:Task.Factory.StartNew isn't running how I expectedTask.Factory.StartNew 没有按我的预期运行
【发布时间】:2017-02-09 20:24:51
【问题描述】:

我一直在尝试确定与我的其余代码同时/并行运行代码的最佳方式,可能使用线程。根据我的阅读,在现代 C# 中使用 Thread 类型是禁忌。最初我以为Parallel.Invoke(),但结果证明这是一个阻塞调用,直到所有内部工作完成。

在我的应用程序中,我不需要等待任何事情完成,我不关心得到结果,我需要完全独立于当前线程的代码。基本上是“一劳永逸”的想法。

据我所知,Task.Factory.StartNew() 是与当前运行的代码同时/并行运行一段代码的正确方法。

基于此,我认为下面的代码会随机打印出“ABABBABABAA”。

void Main()
{
    Task.Factory.StartNew(() =>
    {
        for (int i = 0; i < 10; i++)
        {
            Console.Write("A");
        }
    });

    for (int i = 0; i < 10; i++)
    {
        Console.Write("B");
    }
}

但是,它:

  1. 打印出“BBBBBBBBBBAAAAAAAAAA”
  2. 如果我将 Task.Factory.StartNew 与 for 交换,反之亦然,打印出相同的序列,这看起来很奇怪。

所以这让我认为Task.Factory.StartNew() 从来没有真正将工作安排到另一个线程,就好像调用StartNew 是一个阻塞调用。

考虑到我不需要得到任何结果或等待/await,我是否更容易创建一个新的Thread 并在那里运行我的代码?我对此的唯一问题是使用Thread 似乎与现代最佳实践相反,而“C# 6.0 中的并发”一书指出:

只要你输入 new Thread(),就结束了;您的项目已有旧代码

【问题讨论】:

  • 输出没有什么奇怪的。任务“将在之后的某个时间运行”它被安排。这次恰好是在内联循环运行之后(因为它是在任务安排后立即运行的)并且测试代码没有显示任何有趣的内容。在这两种情况下,在每次循环迭代之间添加 100 毫秒的延迟应该会改变观察结果。
  • 嗯,我明白了。我将循环更改为具有更高的值,现在我看到了很多 AAAAABBBBBAAAA。那么是不是我根本没有给任务安排足够的时间?
  • 在这种情况下 - 是的。此外,大多数线程实际上是交错运行的,而不是真正并发的(这在 1 核系统上可视化是最简单的;尽管 n 核处理器仍将运行 m 个线程,其中 m >>> n)。

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


【解决方案1】:

实际上 Task.Factory.StartNew 确实在单独的线程上运行,它每次都输掉比赛的唯一原因是因为任务创建时间。这是证明它的代码

static void Main(string[] args)
    {
        Task.Factory.StartNew(() =>
        {
            for (int i = 0; i < 10; i++)
            {
                Console.Write("A");
                Thread.Sleep(1);
            }
        });

        for (int i = 0; i < 10; i++)
        {
            Console.Write("B");
            Thread.Sleep(1);
        }
        Console.ReadKey();
    }

【讨论】:

    【解决方案2】:

    在我的应用程序中,我不需要等待任何事情完成,我不关心得到结果,我需要完全独立于当前线程的代码。基本上是“一劳永逸”的想法。

    你确定吗? Fire-and-forget 的真正意思是“我完全可以忽略异常”。由于这不是大多数人想要的,一种常见的方法是将工作排队并保存代表该工作的Task,并在您的程序执行的任何操作的“结束”时加入它,以确保不完全- 即发即弃的工作确实成功完成。

    我一直在尝试确定与我的其余代码同时/并行运行代码的最佳方式,可能使用线程。根据我的阅读,在现代 C# 中使用 Thread 类型是一个禁忌。最初我认为是 Parallel.Invoke(),但结果证明这是一个阻塞调用,直到所有内部工作完成。

    确实,并行代码会阻塞调用线程。这可以通过将并行调用包装在 Task.Run 中来避免(由我书中的配方 7.4 说明)。

    在您的特定情况下(即,在“即发即弃”的场景中),您可以直接删除 await 并拥有一个空的 Task.Run。虽然正如我上面提到的,最好还是把Task 藏在某个地方,然后再把await 藏起来。

    据我所知,Task.Factory.StartNew() 是与当前运行的代码同时/并行运行一段代码的正确方法。

    不,StartNew is dangerous,只能作为最后的手段。正确的技术是Task.Run,如果您确实有并行 工作要做(即,许多受 CPU 限制的代码块),那么围绕 Parallel/PLINQ 的 Task.Run 包装器将是最好的。

    基于此,我认为下面的代码会随机打印出“ABABBABABAA”。

    作为others have noted,这只是一个竞争条件。将工作排队到线程池需要时间,并且计算机可以非常快地数到 10。 (写输出要慢很多,但这里还是太快了)。 Task.Run(或手动 Thread)也会出现同样的问题。

    【讨论】:

      猜你喜欢
      • 2011-06-16
      • 1970-01-01
      • 2015-05-26
      • 1970-01-01
      • 2017-09-28
      • 1970-01-01
      • 2017-09-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多