【问题标题】:Argument value different inside called function被调用函数内部的参数值不同
【发布时间】:2020-04-25 06:19:01
【问题描述】:

我正在使用下面的代码在 C# 中创建多个任务:

private static List<Task> _taskList = new List<Task>();
private static ConcurrentQueue<string> cred = new ConcurrentQueue<string>();

private static void TaskMethod1(string usercred)
{
    // I am doing a bunch of operations here, all of them can be replaced with
    // a sleep for 25 minutes.
    // After all operations are done, enqueue again.
    cred.Enqueue("usercred")
}

private static void TaskMethod()
{
    while(runningService)
    {
        string usercred;
        // This will create more than one task in parallel to run,
        // and each task can take up to 30 minutes to finish.
        while(cred.TryDequeue(out usercred))
        {
            _taskList.Add(Task.Run(() => TaskMethod1(usercred)));
        }
    }
}

internal static void Start()
{
    runningService = true;
    cred.enqueue("user1");
    cred.enqueue("user2");
    cred.enqueue("user3");
    Task1 = Task.Run(() => TaskMethod());
}

我在上面的代码中遇到了一个奇怪的行为。通过在_taskList.Add(Task.Run(() =&gt; TaskMethod1(usercred))); 行放置一个断点,我每次调用TaskMethod1 时都会检查usercred 的值,并且在调用时它不为空,但在其中一种情况下usercred 的值是null 内部TaskMethod1。我不知道这是怎么发生的。

【问题讨论】:

  • 我怀疑你收到了captured variable problem。请在方法 TaskMethod 中添加显示变量 arg1 的所有用法的代码。
  • 用显示TaskMethod中变量用法的代码编辑。
  • 您能否将while(cred.TryDequeue(out usercred)) { _taskList.Add(Task.Run(() =&gt; TaskMethod1(usercred))); } 更改为while(cred.TryDequeue(out usercred)) { var uc = usercred; _taskList.Add(Task.Run(() =&gt; TaskMethod1(uc))); } 并让我们知道问题是否消失?
  • 这个问题是否只在附加调试器时出现,在您使用 Ctrl+F5(不调试)运行程序时消失?我之所以问,是因为您提到了一个断点,并且调试器在多线程代码中不是 100% 可信赖的。
  • 你也知道TaskMethod 方法会创建一个紧密的循环吗?

标签: c# multithreading multitasking


【解决方案1】:

您正在使用Task.Run,其中您正在使用来自 while 循环的变量。您没有将其传递给任务。因此,当任务执行时,它的值会发生变化。

你应该使用

while (runningService)
{
    string usercred;
    // This will create more than one task in parallel to run,
    // and each task can take upto 30 minutes to finish.
    while (cred.TryDequeue(out usercred))
    {
        _taskList.Add(Task.Factory.StartNew((data) => TaskMethod1(data.ToString()), usercred)
    }
}

【讨论】:

  • 使用Task.Factory.StartNew 方法而不是Task.Run 是因为传递参数is considered 是一种性能优化技术(避免关闭对性能敏感的代码路径)。对于普通的应用程序代码,Task.Run 更可取,因为它更具可读性。
【解决方案2】:

您应该 declare 内部 while 循环内的 usercred 变量,以便 Task.Run 内的 lambda 为每个循环捕获一个单独的变量,而不是为所有循环捕获同一个变量。

while(runningService)
{
    while(cred.TryDequeue(out var usercred))
    {
        _taskList.Add(Task.Run(() => TaskMethod1(usercred)));
    }
}

作为旁注,我会考虑使用BlockingCollection 而不是ConcurrentQueue,以便在项目可用之前阻止当前线程,这样我就不必担心无意中创建一个紧密的循环。

【讨论】:

    【解决方案3】:

    以下更改解决了问题。

    private static void TaskMethod()
    {
        while(runningService)
        {
            string usercred;
            // This will create more than one task in parallel to run,
            // and each task can take up to 30 minutes to finish.
            while(cred.TryDequeue(out usercred))
            {
                var uc = usercred;
                _taskList.Add(Task.Run(() => TaskMethod1(uc)));
            }
        }
    }
    

    【讨论】:

    • Theodor 的回答都解决了这个问题。您的代码重新使用了对usercred 的相同引用,因此如果任务启动不够快,下一个cred.TryDequeue(out usercred) 将覆盖usercred 的值。是什么让它null 对我来说仍然是个谜。但我很好奇我的建议是否会奏效,而且确实奏效了。
    猜你喜欢
    • 2013-06-23
    • 1970-01-01
    • 1970-01-01
    • 2021-04-12
    • 2018-10-21
    • 1970-01-01
    • 1970-01-01
    • 2015-02-19
    • 2011-11-15
    相关资源
    最近更新 更多