使用内存缓存时的异步/等待线程安全性

9

我正在查看 http://www.albahari.com/threading/part4.aspx 中有关内存屏障的部分,并尝试创建一个基于async/await的示例,该示例提供了“我们真的需要锁和屏障吗?”下的示例:

public class Program
{
    static void Main(string[] args)
    {
        TestAsync();
        Console.ReadKey(true);
    }

    private static async void TestAsync()
    {
        bool complete = false;
        Func<Task> testFunc = async () =>
        {
            await Task.Delay(1000);
            bool toggle = false;
            while (!complete) toggle = !toggle;
        };

        var task = testFunc();
        Thread.Sleep(2000);
        complete = true;
        await task;
        Console.WriteLine("Done");
    }
}

在没有调试的发布模式下运行,程序将永远无法完成,这与它所基于的原始线程示例相同。
然而,我认为由于上下文保存的方式,使用async/await会防止这些问题。或者说,在使用async/await时仍然适用所有线程安全规则吗?
2个回答

10

实际上这是一个编译优化问题。当你以发布模式编译时,由于某些原因它会预测complete永远不会为真,并无限运行你的应用程序。由于你是基于另一个示例来做的,我猜你已经知道了这一点。但就async / await而言,它并不能被指责。

要使此工作正常,你仍需要将complete设置为volatile变量,像这样:

        static volatile bool complete = false;

这将告诉编译器每个周期都要检查它,并且它将起作用。

我并不是说我同意,但现在发生的情况是编译器一直看到while(!complete)部分没有任何改变,因为没有使用volatile关键字来优化性能。

使其工作的另一种方法是删除编译器的优化。 您可以单击项目属性,然后单击构建选项卡,取消选中“优化代码”。 然后,发布版本就可以运行。


1
好的,就像Eric Lippert在摘要中那样熟练,他肯定是在发牢骚,试图强加一个明显错误的观点。在C#中,布尔(Boolean)字段是原子性的,这意味着它始终是线程安全的。没有任何理由在布尔上放置锁,因为这会导致性能下降。不要相信我,试着测试一下吧,因为我已经这么做了。考虑到布尔是原子性的且始终是线程安全的,我们可以将其用于多线程等操作。但是;为了防止编译器优化变量,我们添加volatile关键字。我只是不同意Eric的看法。 - Michael Puckett II
然而,您可以使用Monitor.Wait和Monitor.Pulse来完成相同的任务(在完成时发出信号),而不需要使用volatile。 - Nabren
@Nabren volatile只是一个关键字,用于消除变量中编译器的假设,以便其按预期工作。Monitor.Wait...仍然引入锁定并降低性能。尝试在控制台应用程序上进行操作,并测试循环的时间。volatile对性能没有任何影响。 - Michael Puckett II
我非常感谢您在我最近的一个问题上提供的帮助,所以我去查看了您的其他答案。我一直在尝试提高自己的多线程问题处理能力,我发现这个答案和您的评论非常有帮助。谢谢。(PS:我也非常欣赏您在SO个人资料中的评论)。 - user4843530
@AgapwIesu 谢谢。太棒了,我真的很感激你。 - Michael Puckett II
显示剩余3条评论

1
问题在于对complete变量的访问没有同步。await将在任务运行之前和之后插入屏障,以确保任务继续看到新值,但请注意,在此示例中,这并没有帮助。在complete = true之后,任务被等待的事实不会插入屏障,而且循环线程也没有在变量读取上进行同步。
编译器允许优化代码,但即使没有优化,代码在实践中也可能永远运行,因为complete变量的值从未在两个线程之间显式可见。
可以通过同步方法(如'volatile')来解决这个问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接