【问题标题】:Creating a local copy on each task, still not providing closure?在每个任务上创建一个本地副本,仍然不提供关闭?
【发布时间】:2018-06-18 14:48:44
【问题描述】:

背景

目前我正在编写一个程序,其中两个线程同时运行,每个线程都访问关键区域并向控制台写入一条消息。该程序涉及两个任务,我表示 12 基于定义为每个任务的局部变量的整数。这是创建和运行任务的代码 sn-p:

static void Main(string[] args)
{
    Task[] tasks = new Task[2];
    Mutex m;

    // create the named mutex
    m = new Mutex(false, "mutex_name");

    // create two tasks that will each access the same critical region
    for (int i = 0; i < tasks.Length; i++)
    {
        tasks[i] = Task.Run(() =>
        {
            int iCopy = i;
            int iterations = 20;

            // run code that continuously enters the critical region
            for (int j = 0; j < iterations; j++)
            {
                WriteConsole(m, iCopy);
            }
        });
    }

    // wait for all of the tasks to finish
    foreach (var task in tasks)
    {
        task.Wait();
    }

    // wait for the user to exit the program
    Console.ReadKey();
}

好的,请注意,我专门遵循了在每个任务上创建迭代变量的本地副本的做法。我已经这样做了,在过去的情况下,它提供了关闭。这一次,我最终得到的两个任务都包含值 2,这表明即使我已经制作了副本,变量仍然引用原始 for 循环。为什么?

另外,这里是WriteConsole函数的代码:

static void WriteConsole(Mutex m, int name)
{
    // enter the critical region
    m.WaitOne();

    // acknowledge that we've entered the critical region
    Console.WriteLine("Task: " + name + " has entered the critical region.");

    // hold the mutex for a little while
    Thread.Sleep(1000);

    // acknowledge that we've left the critical region
    Console.WriteLine("Task: " + name + " will now leave the critical region.");

    // leave the critical region
    m.ReleaseMutex();
}

这是上面的程序输出,我已经截断了一点,因为有这么多,因为显然我们处于一个循环中。在任何情况下,循环都会以每行打印 Task: 2:

结束

Task: 2 已进入临界区。

Task: 2 现在将离开临界区。

Task: 2 已进入临界区。

Task: 2 现在将离开临界区。

Task: 2 已进入临界区。

“我尝试过的”

我尝试展开 for 循环以查看是否还有其他问题,或者这是否确实是循环/变量闭包的问题。当我展开循环时,我的输出看起来好多了:

Task: 0 已进入临界区。

Task: 0 现在将离开临界区。

Task: 1 已进入临界区。

Task: 1 现在将离开临界区。

Task: 0 已进入临界区。

Task: 0 现在将离开临界区。

Task: 1 已进入临界区。

Task: 1 现在将离开临界区。

Task: 0 已进入临界区。

Task: 0 现在将离开临界区。

Task: 0 已进入临界区。

Task: 0 现在将离开临界区。

Task: 1 已进入临界区。

Task: 1 现在将离开临界区。

当我说我已经展开 for 循环时,很可能知道我的意思,但我不会做任何假设,并为此包含代码 sn-p:

tasks[0] = Task.Run(() =>
{
    int iterations = 20;

    // run code that continuously enters the critical region
    for (int j = 0; j < iterations; j++)
    {
        WriteConsole(m, 0);
    }
});
tasks[1] = Task.Run(() =>
{
    int iterations = 20;

    // run code that continuously enters the critical region
    for (int j = 0; j < iterations; j++)
    {
        WriteConsole(m, 1);
    }
});

根据上述测试,此时看起来确实是一个关闭问题。现在,我要补充的最后一件事是,无论循环是否滚动/展开,两个任务都在执行。我已经能够设置断点,并看到任一任务在调试会话期间都按应有的方式执行。

问题

真正让我感动的是“创建本地副本”方法似乎没有提供闭包。

【问题讨论】:

    标签: c# multithreading for-loop closures task


    【解决方案1】:

    这是因为您在任务中创建了iCopy,该任务仅在Task.Run 完成准备并开始执行您的代码时启动。换句话说,修改i 的循环与Task.Runii 中捕获i 的值@ 的值@ 调用的代码之间存在竞争,for 循环赢得了竞争。

    将声明移到任务之外解决了这个问题:

    for (int i = 0; i < tasks.Length; i++) {
        int iCopy = i;
        tasks[i] = Task.Run(() => {
            int iterations = 20;
            // run code that continuously enters the critical region
            for (int j = 0; j < iterations; j++) {
                WriteConsole(m, iCopy);
            }
        });
    }
    

    Demo.

    【讨论】:

    • 谢谢,只是一个旁注。为什么您使用的那个演示应用程序让任务 0 进入关键区域完成......然后任务 1 进入关键区域完成?只是运气好?
    • 这让我对我过去做过的一些事情感到害怕......有没有一种情况说......任务的准备恰好赢得了比赛?
    • @Snoop 我将等待时间更改为100,这样 ideone 就不会在他们在网站上设置的 5 秒超时后退回任务。
    • @Snoop 在线程启动和分配iCopy = i 发生之间,有足够的准备时间让您的for 循环完成其运行。您可以查看source code 以了解Task.Run 的作用。代码从第 5614 行开始。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-02-18
    • 1970-01-01
    • 2018-09-20
    • 2018-09-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多