【问题标题】:Thread safety on async/await with memory caching带内存缓存的异步/等待线程安全
【发布时间】:2017-06-15 03:38:37
【问题描述】:

我正在查看http://www.albahari.com/threading/part4.aspx 中描述的内存屏障部分 并尝试制作“我们真的需要锁和障碍吗?”下提供的示例的异步/等待版本:

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 时所有线程安全规则仍然适用?

【问题讨论】:

    标签: c# .net multithreading caching asynchronous


    【解决方案1】:

    这实际上是一个编译优化问题。当您出于某种原因在发行版中编译时,它预测完成永远不会是真的并且无限地运行您的应用程序。既然你已经基于另一个例子,我猜你已经知道了。但就 async / await 而言,这不能怪罪。

    要完成这项工作,您仍然需要将 complete 设置为 volatile 变量,如下所示:

            static volatile bool complete = false;
    

    这将告诉编译器在每个周期都检查它,它会工作。

    我并不是说我同意它,但正在发生的事情是编译器一直看到完全没有变化,直到 while(!complete) 部分,因为没有 volatile 关键字,它决定它永远不会更改以优化性能。

    实现这项工作的另一种方法是删除编译器优化。您可以单击项目属性,然后单击构建选项卡并取消选中“优化代码”。然后它将在发布中工作。

    【讨论】:

    • 虽然这确实可以解决问题,但Eric Lippert himself discourages the use of volatile
    • 好吧,就像 Eric Lippert 在总结中的技巧一样,他肯定是在咆哮而不是任何事情,并用它来强迫一个绝对错误的观点。在 C# 中,布尔(布尔)字段是原子的,这意味着它始终是线程安全的。绝对没有理由在布尔值周围加锁,因为这会导致性能下降。不相信我,尝试测试它,因为我有。看到 bool 是原子的并且始终是线程安全的,我们可以将它用于多线程这样的事情。然而;为了防止编译器优化我们添加 volatile 的变量。我只是不同意埃里克。
    • 谢谢,我想了这么多,认为使用 async/await 时保存的上下文可能会更多
    • 但是,您可以使用 Monitor.Wait 和 Monitor.Pulse 来完成不需要使用 volatile 的相同操作(完成时发出信号)。
    • @Nabren volatile 只是从变量中删除编译器假设的关键字;使其按预期工作。 Monitor.Wait... 仍然会引入锁并降低性能。尝试在控制台应用程序上解决它并在循环中测试时间。易失性对性能的影响为零。
    【解决方案2】:

    问题是对complete 变量的访问不同步。 await 将在任务运行之前和之后插入障碍,以确保任务继续看到新值,但请注意在此示例中这无济于事。在complete = true之后,等待任务的事实并没有插入屏障,无论如何,循环线程也没有在变量读取上同步。

    允许编译器优化代码,但即使没有,代码也可能在实践中永远运行,因为complete 变量的值永远不会在两个线程之间显式显示。

    这可以通过同步方法来解决,比如 'volatile'。

    【讨论】:

      猜你喜欢
      • 2014-05-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-08
      • 1970-01-01
      • 2013-02-15
      相关资源
      最近更新 更多