【问题标题】:why C# ThreadLocal does not work as expected with Task in TPL?为什么 C# ThreadLocal 在 TPL 中的 Task 不能按预期工作?
【发布时间】:2017-10-15 10:02:51
【问题描述】:

无法理解 apress 书中关于 TPL 中线程本地与任务构造的误用案例的示例。

为什么没有达到预期结果的10000?

谁能对下面程序的程序流程给出更详细的解释,哪些行立即执行,一些行及时异步?执行的顺序和顺序?

using System;
using System.Threading;
using System.Threading.Tasks;

namespace Listing_05 {

class BankAccount {
    public int Balance {
        get;
        set;
    }
}

class Listing_05 {

    static void Main(string[] args) {

        // create the bank account instance
        BankAccount account = new BankAccount();

        // create an array of tasks
        Task<int>[] tasks = new Task<int>[10];

        // create the thread local storage
        ThreadLocal<int> tls = new ThreadLocal<int>(() => {
            Console.WriteLine("Value factory called for value: {0}",
               account.Balance);
            return account.Balance;
        });

        for (int i = 0; i < 10; i++) {
            // create a new task
            tasks[i] = new Task<int>(() => {

                // enter a loop for 1000 balance updates
                for (int j = 0; j < 1000; j++) {
                    // update the TLS balance
                    tls.Value++;
                }

                // return the updated balance
                return tls.Value;

            });

            // start the new task
            tasks[i].Start();
        }

        // get the result from each task and add it to
        // the balance
        for (int i = 0; i < 10; i++) {
            //added by myself to see any hints but still cannot have insights
            Console.WriteLine("task {0} results {1}", i, tasks[i].Result);
            //end of my editing

            account.Balance += tasks[i].Result;
        }

        // write out the counter value
        Console.WriteLine("Expected value {0}, Balance: {1}",
            10000, account.Balance);

        // wait for input before exiting
        Console.WriteLine("Press enter to finish");
        Console.ReadLine();
    }
}

}

结果在使用 8 核 i7 cpu 的计算机中,应为 8 个线程。运行多次及以下是多次执行中的 2 次。

不了解程序如何以这种方式工作和表现

【问题讨论】:

  • 是的,在提出上述问题之前,我只是在上面的链接上进行了谷歌搜索,但也许我是这个主题的新手,所以我很难理解程序为什么会这样。到目前为止,我的理解是 ThreadLocal 只是线程本地的数据。 Task 是声明性的,您描述未知工作线程要完成的任务。不知道线程本地初始化数据是否会由于 10 个任务中的一些重用已在 10 个任务中的一些任务中修改的相同线程数据而崩溃?有人能描述一下这个例子的执行是怎样的吗……应该推迟运行某些行吗?
  • @Cuda,您的价值工厂中有两个account.Balance。两者都有权返回不同的结果。
  • 任务通常在线程池提供的线程上运行。线程池最大的特点是它可以重用线程。因此,两个任务很容易最终使用相同的 ThreadLocal 变量,这是所提供示例中的一个错误。 AsyncLocal 类提供了一个有用的替代品。
  • 无法保证调度程序将为您的代码使用多少线程

标签: c# multithreading thread-safety task-parallel-library


【解决方案1】:

任务提供了一种同时运行代码的机制。这并不意味着它们必须在单独的线程上运行,或者即使使用多个线程也不会被重用。

不要尝试将线程本地存储与任务一起使用。

替代存储选项是在任务的 lambda 函数中使用闭包,或者创建一个类并将数据放在该类上,并使用该类的方法作为任务的被调用者。反正我个人觉得这样干净多了。

Hans Passant 还在 cmets 中提到了 AsyncLocal,这值得研究(我自己没有使用过,所以无法评论)。

【讨论】:

  • 所以你的意思是它可以是 1 个线程同时执行任务,也可以是多个线程同时执行任务,不能保证线程的重用,所以不要使用仅对单个线程本地的线程本地存储线? Tasks应该用什么来存储数据?
猜你喜欢
  • 2021-05-30
  • 2020-03-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多